org.codehaus.groovy.runtime.StringGroovyMethods Maven / Gradle / Ivy
/*
* Copyright 2003-2013 the original author or authors.
*
* Licensed 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.codehaus.groovy.runtime;
import groovy.lang.Closure;
import groovy.lang.EmptyRange;
import groovy.lang.GString;
import groovy.lang.IntRange;
import groovy.lang.Range;
import org.codehaus.groovy.runtime.callsite.BooleanClosureWrapper;
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.callClosureForLine;
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.each;
/**
* This class defines new groovy methods which appear on String-related JDK
* classes (String, CharSequence, Matcher) inside the Groovy environment.
* Static methods are used with the
* first parameter being the destination class,
* e.g.. public static String reverse(String self)
* provides a reverse()
method for String
.
*
* NOTE: While this class contains many 'public' static methods, it is
* primarily regarded as an internal class (its internal package name
* suggests this also). We value backwards compatibility of these
* methods when used within Groovy but value less backwards compatibility
* at the Java method call level. I.e. future versions of Groovy may
* remove or move a method call in this file but would normally
* aim to keep the method available from within Groovy.
*
* @author James Strachan
* @author Jeremy Rayner
* @author Sam Pullara
* @author Rod Cope
* @author Guillaume Laforge
* @author John Wilson
* @author Hein Meling
* @author Dierk Koenig
* @author Pilho Kim
* @author Marc Guillemot
* @author Russel Winder
* @author bing ran
* @author Jochen Theodorou
* @author Paul King
* @author Michael Baehr
* @author Joachim Baumann
* @author Alex Tkachman
* @author Ted Naleid
* @author Brad Long
* @author Jim Jagielski
* @author Rodolfo Velasco
* @author jeremi Joslin
* @author Hamlet D'Arcy
* @author Cedric Champeau
* @author Tim Yates
* @author Dinko Srkoc
* @author Pascal Lombard
* @author Christophe Charles
*/
public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
static String lineSeparator = null;
/**
* Coerce a string (an instance of CharSequence) to a boolean value.
* A string is coerced to false if it is of length 0,
* and to true otherwise.
*
* @param string the character sequence
* @return the boolean value
* @since 1.7.0
*/
public static boolean asBoolean(CharSequence string) {
return string.length() > 0;
}
/**
* Coerce a Matcher instance to a boolean value.
*
* @param matcher the matcher
* @return the boolean value
* @since 1.7.0
*/
public static boolean asBoolean(Matcher matcher) {
RegexSupport.setLastMatcher(matcher);
return matcher.find();
}
/**
*
Provides a method to perform custom 'dynamic' type conversion
* to the given class using the as
operator.
*
* @param self a CharSequence
* @param c the desired class
* @return the converted object
* @see #asType(String, Class)
* @since 1.8.2
*/
public static T asType(CharSequence self, Class c) {
return asType(self.toString(), c);
}
/**
* Converts the GString to a File, or delegates to the default
* {@link org.codehaus.groovy.runtime.DefaultGroovyMethods#asType(Object, Class)}
*
* @param self a GString
* @param c the desired class
* @return the converted object
* @since 1.5.0
*/
@SuppressWarnings("unchecked")
public static T asType(GString self, Class c) {
if (c == File.class) {
return (T) new File(self.toString());
} else if (Number.class.isAssignableFrom(c) || c.isPrimitive()) {
return asType(self.toString(), c);
}
return DefaultGroovyMethods.asType((Object) self, c);
}
/**
* Provides a method to perform custom 'dynamic' type conversion
* to the given class using the as
operator.
* Example: '123' as Double
*
* By default, the following types are supported:
*
* - List
* - BigDecimal
* - BigInteger
* - Long
* - Integer
* - Short
* - Byte
* - Character
* - Double
* - Float
* - File
* - Subclasses of Enum (Java 5 and above)
*
* If any other type is given, the call is delegated to
* {@link org.codehaus.groovy.runtime.DefaultGroovyMethods#asType(Object, Class)}.
*
* @param self a String
* @param c the desired class
* @return the converted object
* @since 1.0
*/
@SuppressWarnings("unchecked")
public static T asType(String self, Class c) {
if (c == List.class) {
return (T) toList(self);
} else if (c == BigDecimal.class) {
return (T) toBigDecimal(self);
} else if (c == BigInteger.class) {
return (T) toBigInteger(self);
} else if (c == Long.class || c == Long.TYPE) {
return (T) toLong(self);
} else if (c == Integer.class || c == Integer.TYPE) {
return (T) toInteger(self);
} else if (c == Short.class || c == Short.TYPE) {
return (T) toShort(self);
} else if (c == Byte.class || c == Byte.TYPE) {
return (T) Byte.valueOf(self.trim());
} else if (c == Character.class || c == Character.TYPE) {
return (T) toCharacter(self);
} else if (c == Double.class || c == Double.TYPE) {
return (T) toDouble(self);
} else if (c == Float.class || c == Float.TYPE) {
return (T) toFloat(self);
} else if (c == File.class) {
return (T) new File(self);
} else if (DefaultTypeTransformation.isEnumSubclass(c)) {
return (T) InvokerHelper.invokeMethod(c, "valueOf", new Object[]{ self });
}
return DefaultGroovyMethods.asType((Object) self, c);
}
/**
* Turns a CharSequence into a regular expression Pattern
*
* @param self a String to convert into a regular expression
* @return the regular expression pattern
* @since 1.8.2
*/
public static Pattern bitwiseNegate(CharSequence self) {
return Pattern.compile(self.toString());
}
/**
* Turns a String into a regular expression Pattern
*
* @param self a String to convert into a regular expression
* @return the regular expression pattern
* @since 1.5.0
*/
public static Pattern bitwiseNegate(String self) {
return Pattern.compile(self);
}
/**
* Convenience method to capitalize the first letter of a CharSequence.
*
* @param self The CharSequence to capitalize
* @return The capitalized CharSequence
* @see #capitalize(String)
* @since 1.8.2
*/
public static CharSequence capitalize(CharSequence self) {
return capitalize(self.toString());
}
/**
* Convenience method to capitalize the first letter of a string
* (typically the first letter of a word). Example usage:
*
* assert 'h'.capitalize() == 'H'
* assert 'hello'.capitalize() == 'Hello'
* assert 'hello world'.capitalize() == 'Hello world'
* assert 'Hello World' ==
* 'hello world'.split(' ').collect{ it.capitalize() }.join(' ')
*
*
* @param self The string to capitalize
* @return The capitalized String
* @since 1.7.3
*/
public static String capitalize(String self) {
if (self == null || self.length() == 0) return self;
return Character.toUpperCase(self.charAt(0)) + self.substring(1);
}
/**
* Pad a CharSequence to a minimum length specified by numberOfChars by adding the space character around it as many times as needed so that it remains centered.
*
* @param self a CharSequence object
* @param numberOfChars the total minimum number of characters of the resulting CharSequence
* @return the CharSequence centered with padded characters around it
* @see #center(String, Number)
* @since 1.8.2
*/
public static CharSequence center(CharSequence self, Number numberOfChars) {
return center(self.toString(), numberOfChars);
}
/**
* Pad a CharSequence to a minimum length specified by numberOfChars, appending the supplied padding CharSequence around the original as many times as needed keeping it centered.
*
* @param self a CharSequence object
* @param numberOfChars the total minimum number of characters of the resulting CharSequence
* @param padding the characters used for padding
* @return the CharSequence centered with padded characters around it
* @see #center(String, Number, String)
* @since 1.8.2
*/
public static CharSequence center(CharSequence self, Number numberOfChars, CharSequence padding) {
return center(self.toString(), numberOfChars, padding.toString());
}
/**
* Pad a String to a minimum length specified by numberOfChars by adding the space character around it as many times as needed so that it remains centered.
*
* If the String is already the same size or bigger than the target numberOfChars, then the original String is returned. An example:
*
* ['A', 'BB', 'CCC', 'DDDD'].each{ println '|' + it.center(6) + '|' }
*
* will produce output like:
*
* | A |
* | BB |
* | CCC |
* | DDDD |
*
*
* @param self a String object
* @param numberOfChars the total minimum number of characters of the resulting string
* @return the String centered with padded characters around it
* @see #center(String, Number, String)
* @since 1.0
*/
public static String center(String self, Number numberOfChars) {
return center(self, numberOfChars, " ");
}
/**
* Pad a String to a minimum length specified by numberOfChars, appending the supplied padding String around the original as many times as needed keeping it centered.
*
* If the String is already the same size or bigger than the target numberOfChars, then the original String is returned. An example:
*
* ['A', 'BB', 'CCC', 'DDDD'].each{ println '|' + it.center(6, '+') + '|' }
*
* will produce output like:
*
* |++A+++|
* |++BB++|
* |+CCC++|
* |+DDDD+|
*
*
* @param self a String object
* @param numberOfChars the total minimum number of characters of the resulting string
* @param padding the characters used for padding
* @return the String centered with padded characters around it
* @since 1.0
*/
public static String center(String self, Number numberOfChars, String padding) {
int numChars = numberOfChars.intValue();
if (numChars <= self.length()) {
return self;
} else {
int charsToAdd = numChars - self.length();
String semiPad = charsToAdd % 2 == 1 ?
getPadding(padding, charsToAdd / 2 + 1) :
getPadding(padding, charsToAdd / 2);
if (charsToAdd % 2 == 0)
return semiPad + self + semiPad;
else
return semiPad.substring(0, charsToAdd / 2) + self + semiPad;
}
}
/**
* Provide an implementation of contains() like
* {@link java.util.Collection#contains(Object)} to make CharSequences more polymorphic.
*
* @param self a CharSequence
* @param text the CharSequence to look for
* @return true if this CharSequence contains the given text
* @see #contains(String, String)
* @since 1.8.2
*/
public static boolean contains(CharSequence self, CharSequence text) {
return contains(self.toString(), text.toString());
}
/**
* Provide an implementation of contains() like
* {@link java.util.Collection#contains(Object)} to make Strings more polymorphic.
* This method is not required on JDK 1.5 onwards
*
* @param self a String
* @param text a String to look for
* @return true if this string contains the given text
* @since 1.0
*/
public static boolean contains(String self, String text) {
int idx = self.indexOf(text);
return idx >= 0;
}
/**
* Count the number of occurrences of a sub CharSequence.
*
* @param self a CharSequence
* @param text a sub CharSequence
* @return the number of occurrences of the given CharSequence inside this CharSequence
* @see #count(String, String)
* @since 1.8.2
*/
public static int count(CharSequence self, CharSequence text) {
return count(self.toString(), text.toString());
}
/**
* Count the number of occurrences of a substring.
*
* @param self a String
* @param text a substring
* @return the number of occurrences of the given string inside this String
* @since 1.0
*/
public static int count(String self, String text) {
int answer = 0;
for (int idx = 0; true; idx++) {
idx = self.indexOf(text, idx);
// break once idx goes to -1 or for case of empty string once
// we get to the end to avoid JDK library bug (see GROOVY-5858)
if (idx < answer) break;
++answer;
}
return answer;
}
private static StringBufferWriter createStringBufferWriter(StringBuffer self) {
return new StringBufferWriter(self);
}
private static StringWriter createStringWriter(String self) {
StringWriter answer = new StringWriter();
answer.write(self);
return answer;
}
/**
* Return a CharSequence with lines (separated by LF, CR/LF, or CR)
* terminated by the platform specific line separator.
*
* @param self a CharSequence object
* @return the denormalized CharSequence
* @see #denormalize(String)
* @since 1.8.2
*/
public static CharSequence denormalize(final CharSequence self) {
return denormalize(self.toString());
}
/**
* Return a String with lines (separated by LF, CR/LF, or CR)
* terminated by the platform specific line separator.
*
* @param self a String object
* @return the denormalized string
* @since 1.6.0
*/
public static String denormalize(final String self) {
// Don't do this in static initializer because we may never be needed.
// TODO: Put this lineSeparator property somewhere everyone can use it.
if (lineSeparator == null) {
final StringWriter sw = new StringWriter(2);
try {
// We use BufferedWriter rather than System.getProperty because
// it has the security manager rigamarole to deal with the possible exception.
final BufferedWriter bw = new BufferedWriter(sw);
bw.newLine();
bw.flush();
lineSeparator = sw.toString();
} catch (IOException ioe) {
// This shouldn't happen, but this is the same default used by
// BufferedWriter on a security exception.
lineSeparator = "\n";
}
}
final int len = self.length();
if (len < 1) {
return self;
}
final StringBuilder sb = new StringBuilder((110 * len) / 100);
int i = 0;
while (i < len) {
final char ch = self.charAt(i++);
switch (ch) {
case '\r':
sb.append(lineSeparator);
// Eat the following LF if any.
if ((i < len) && (self.charAt(i) == '\n')) {
++i;
}
break;
case '\n':
sb.append(lineSeparator);
break;
default:
sb.append(ch);
break;
}
}
return sb.toString();
}
/**
* Drops the given number of chars from the head of this CharSequence
* if they are available.
*
* def text = "Groovy"
* assert text.drop( 0 ) == 'Groovy'
* assert text.drop( 2 ) == 'oovy'
* assert text.drop( 7 ) == ''
*
*
* @param self the original CharSequence
* @param num the number of characters to drop from this iterator
* @return a CharSequence consisting of all characters except the first num
ones,
* or else an empty String, if this CharSequence has less than num
characters.
* @since 1.8.1
*/
public static CharSequence drop(CharSequence self, int num) {
if( num <= 0 ) {
return self ;
}
if( self.length() <= num ) {
return self.subSequence( 0, 0 ) ;
}
return self.subSequence(num, self.length()) ;
}
/**
* Create a suffix of the given CharSequence by dropping as many characters as possible from the
* front of the original CharSequence such that calling the given closure condition evaluates to
* true when passed each of the dropped characters.
*
*
* def text = "Groovy"
* assert text.dropWhile{ false } == 'Groovy'
* assert text.dropWhile{ true } == ''
* assert text.dropWhile{ it < 'Z' } == 'roovy'
* assert text.dropWhile{ it != 'v' } == 'vy'
*
*
* @param self the original CharSequence
* @param condition the closure that while continuously evaluating to true will cause us to drop elements from
* the front of the original CharSequence
* @return the shortest suffix of the given CharSequence such that the given closure condition
* evaluates to true for each element dropped from the front of the CharSequence
* @since 2.0.0
*/
public static CharSequence dropWhile(CharSequence self, Closure condition) {
int num = 0;
BooleanClosureWrapper bcw = new BooleanClosureWrapper(condition);
while (num < self.length()) {
char value = self.charAt(num);
if (bcw.call(value)) {
num += 1;
} else {
break;
}
}
return drop(self, num);
}
/**
* Iterates through this CharSequence line by line. Each line is passed
* to the given 1 or 2 arg closure. If a 2 arg closure is found
* the line count is passed as the second argument.
*
* @param self a CharSequence
* @param closure a closure
* @return the last value returned by the closure
* @throws java.io.IOException if an error occurs
* @see #eachLine(String, groovy.lang.Closure)
* @since 1.8.2
*/
public static T eachLine(CharSequence self, Closure closure) throws IOException {
return eachLine(self.toString(), closure);
}
/**
* Iterates through this CharSequence line by line. Each line is passed
* to the given 1 or 2 arg closure. If a 2 arg closure is found
* the line count is passed as the second argument.
*
* @param self a CharSequence
* @param firstLine the line number value used for the first line (default is 1, set to 0 to start counting from 0)
* @param closure a closure (arg 1 is line, optional arg 2 is line number)
* @return the last value returned by the closure
* @throws java.io.IOException if an error occurs
* @see #eachLine(String, int, groovy.lang.Closure)
* @since 1.8.2
*/
public static T eachLine(CharSequence self, int firstLine, Closure closure) throws IOException {
return eachLine(self.toString(), firstLine, closure);
}
/**
* Iterates through this String line by line. Each line is passed
* to the given 1 or 2 arg closure. If a 2 arg closure is found
* the line count is passed as the second argument.
*
* @param self a String
* @param closure a closure
* @return the last value returned by the closure
* @throws java.io.IOException if an error occurs
* @see #eachLine(String, int, groovy.lang.Closure)
* @since 1.5.5
*/
public static T eachLine(String self, Closure closure) throws IOException {
return eachLine(self, 0, closure);
}
/**
* Iterates through this String line by line. Each line is passed
* to the given 1 or 2 arg closure. If a 2 arg closure is found
* the line count is passed as the second argument.
*
* @param self a String
* @param firstLine the line number value used for the first line (default is 1, set to 0 to start counting from 0)
* @param closure a closure (arg 1 is line, optional arg 2 is line number)
* @return the last value returned by the closure
* @throws java.io.IOException if an error occurs
* @since 1.5.7
*/
public static T eachLine(String self, int firstLine, Closure closure) throws IOException {
int count = firstLine;
T result = null;
for (String line : readLines(self)) {
result = callClosureForLine(closure, line, count);
count++;
}
return result;
}
/**
* Iterate through this String a character at a time collecting either the
* original character or a transformed replacement String. The {@code transform}
* Closure should return {@code null} to indicate that no transformation is
* required for the given character.
*
*
* assert "Groovy".collectReplacements{ it == 'o' ? '_O_' : null } == 'Gr_O__O_vy'
* assert "B&W".collectReplacements{ it == '&' ? '&' : null } == 'B&W'
*
*
* @param orig the original String
* @return A new string in which all characters that require escaping
* have been replaced with the corresponding replacements
* as determined by the {@code transform} Closure.
*/
public static String collectReplacements(String orig, Closure transform) {
if (orig == null) return orig;
StringBuilder sb = null; // lazy create for edge-case efficiency
for (int i = 0, len = orig.length(); i < len; i++) {
final char ch = orig.charAt(i);
final String replacement = transform.call(ch);
if (replacement != null) {
// output differs from input; we write to our local buffer
if (sb == null) {
sb = new StringBuilder((int) (1.1 * len));
sb.append(orig.substring(0, i));
}
sb.append(replacement);
} else if (sb != null) {
// earlier output differs from input; we write to our local buffer
sb.append(ch);
}
}
return sb == null ? orig : sb.toString();
}
/**
* Process each regex group matched substring of the given CharSequence. If the closure
* parameter takes one argument, an array with all match groups is passed to it.
* If the closure takes as many arguments as there are match groups, then each
* parameter will be one match group.
*
* @param self the source CharSequence
* @param regex a Regex CharSequence
* @param closure a closure with one parameter or as much parameters as groups
* @return the source CharSequence
* @see #eachMatch(String, String, groovy.lang.Closure)
* @since 1.8.2
*/
public static String eachMatch(CharSequence self, CharSequence regex, Closure closure) {
return eachMatch(self.toString(), regex.toString(), closure);
}
/**
* Process each regex group matched substring of the given pattern. If the closure
* parameter takes one argument, an array with all match groups is passed to it.
* If the closure takes as many arguments as there are match groups, then each
* parameter will be one match group.
*
* @param self the source CharSequence
* @param pattern a regex Pattern
* @param closure a closure with one parameter or as much parameters as groups
* @return the source CharSequence
* @see #eachMatch(String, java.util.regex.Pattern, groovy.lang.Closure)
* @since 1.8.2
*/
public static String eachMatch(CharSequence self, Pattern pattern, Closure closure) {
return eachMatch(self.toString(), pattern, closure);
}
/**
* Process each regex group matched substring of the given pattern. If the closure
* parameter takes one argument, an array with all match groups is passed to it.
* If the closure takes as many arguments as there are match groups, then each
* parameter will be one match group.
*
* @param self the source string
* @param pattern a regex Pattern
* @param closure a closure with one parameter or as much parameters as groups
* @return the source string
* @since 1.6.1
*/
public static String eachMatch(String self, Pattern pattern, Closure closure) {
Matcher m = pattern.matcher(self);
each(m, closure);
return self;
}
/**
* Process each regex group matched substring of the given string. If the closure
* parameter takes one argument, an array with all match groups is passed to it.
* If the closure takes as many arguments as there are match groups, then each
* parameter will be one match group.
*
* @param self the source string
* @param regex a Regex string
* @param closure a closure with one parameter or as much parameters as groups
* @return the source string
* @since 1.6.0
*/
public static String eachMatch(String self, String regex, Closure closure) {
return eachMatch(self, Pattern.compile(regex), closure);
}
/**
* Expands all tabs into spaces with tabStops of size 8.
*
* @param self A CharSequence to expand
* @return The expanded CharSequence
* @see #expand(String)
* @since 1.8.2
*/
public static CharSequence expand(CharSequence self) {
return expand(self.toString(), 8);
}
/**
* Expands all tabs into spaces. If the CharSequence has multiple
* lines, expand each line - restarting tab stops at the start
* of each line.
*
* @param self A CharSequence to expand
* @param tabStop The number of spaces a tab represents
* @return The expanded CharSequence
* @see #expand(String, int)
* @since 1.8.2
*/
public static CharSequence expand(CharSequence self, int tabStop) {
return expand(self.toString(), tabStop);
}
/**
* Expands all tabs into spaces with tabStops of size 8.
*
* @param self A String to expand
* @return The expanded String
* @since 1.7.3
* @see #expand(String, int)
*/
public static String expand(String self) {
return expand(self, 8);
}
/**
* Expands all tabs into spaces. If the String has multiple
* lines, expand each line - restarting tab stops at the start
* of each line.
*
* @param self A String to expand
* @param tabStop The number of spaces a tab represents
* @return The expanded String
* @since 1.7.3
*/
public static String expand(String self, int tabStop) {
if (self.length() == 0) return self;
try {
StringBuilder builder = new StringBuilder();
for (String line : readLines(self)) {
builder.append(expandLine(line, tabStop));
builder.append("\n");
}
// remove the normalized ending line ending if it was not present
if (!self.endsWith("\n")) {
builder.deleteCharAt(builder.length() - 1);
}
return builder.toString();
} catch (IOException e) {
/* ignore */
}
return self;
}
/**
* Expands all tabs into spaces. Assumes the CharSequence represents a single line of text.
*
* @param self A line to expand
* @param tabStop The number of spaces a tab represents
* @return The expanded CharSequence
* @see #expandLine(String, int)
* @since 1.8.2
*/
public static CharSequence expandLine(CharSequence self, int tabStop) {
return expandLine(self.toString(), tabStop);
}
/**
* Expands all tabs into spaces. Assumes the String represents a single line of text.
*
* @param self A line to expand
* @param tabStop The number of spaces a tab represents
* @return The expanded String
* @since 1.7.3
*/
public static String expandLine(String self, int tabStop) {
int index;
while ((index = self.indexOf('\t')) != -1) {
StringBuilder builder = new StringBuilder(self);
int count = tabStop - index % tabStop;
builder.deleteCharAt(index);
for (int i = 0; i < count; i++) builder.insert(index, " ");
self = builder.toString();
}
return self;
}
/**
* Finds the first occurrence of a regular expression CharSequence within a CharSequence.
*
* @param self a CharSequence
* @param regex the capturing regex
* @return a CharSequence containing the matched portion, or null if the regex doesn't match
* @see #find(String, java.util.regex.Pattern)
* @since 1.8.2
*/
public static CharSequence find(CharSequence self, CharSequence regex) {
return find(self.toString(), Pattern.compile(regex.toString()));
}
/**
* Returns the result of calling a closure with the first occurrence of a regular expression found within a CharSequence.
* If the regex doesn't match, the closure will not be called and find will return null.
*
* @param self a CharSequence
* @param regex the capturing regex CharSequence
* @param closure the closure that will be passed the full match, plus each of the capturing groups
* @return a CharSequence containing the result of the closure, or null if the regex pattern doesn't match
* @see #find(String, java.util.regex.Pattern, groovy.lang.Closure)
* @since 1.8.2
*/
public static CharSequence find(CharSequence self, CharSequence regex, Closure closure) {
return find(self.toString(), Pattern.compile(regex.toString()), closure);
}
/**
* Finds the first occurrence of a compiled regular expression Pattern within a CharSequence.
*
* @param self a CharSequence
* @param pattern the compiled regex Pattern
* @return a CharSequence containing the matched portion, or null if the regex pattern doesn't match
* @see #find(String, java.util.regex.Pattern)
* @since 1.8.2
*/
public static CharSequence find(CharSequence self, Pattern pattern) {
return find(self.toString(), pattern);
}
/**
* Returns the result of calling a closure with the first occurrence of a regular expression found within a
* CharSequence. If the regex doesn't match, the closure will not be called and find will return null.
*
* @param self a CharSequence
* @param pattern the compiled regex Pattern
* @param closure the closure that will be passed the full match, plus each of the capturing groups
* @return a CharSequence containing the result of the closure, or null if the regex pattern doesn't match
* @see #find(String, java.util.regex.Pattern, groovy.lang.Closure)
* @since 1.8.2
*/
public static CharSequence find(CharSequence self, Pattern pattern, Closure closure) {
return find(self.toString(), pattern, closure);
}
/**
* Finds the first occurrence of a compiled regular expression Pattern within a String.
* If the pattern doesn't match, null will be returned.
*
* For example, if the pattern doesn't match the result is null:
*
* assert null == "New York, NY".find(~/\d{5}/)
*
*
* If it does match, we get the matching string back:
*
* assert "10292" == "New York, NY 10292-0098".find(~/\d{5}/)
*
*
* If we have capture groups in our expression, the groups are ignored and
* we get back the full match:
*
* assert "10292-0098" == "New York, NY 10292-0098".find(~/(\d{5})-?(\d{4})/)
*
* If you need to work with capture groups, then use the closure version
* of this method or use Groovy's matcher operators or use eachMatch.
*
* @param self a String
* @param pattern the compiled regex Pattern
* @return a String containing the matched portion, or null if the regex pattern doesn't match
* @since 1.6.1
*/
public static String find(String self, Pattern pattern) {
Matcher matcher = pattern.matcher(self);
if (matcher.find()) {
return matcher.group(0);
}
return null;
}
/**
* Returns the result of calling a closure with the first occurrence of a compiled regular expression found within a String.
* If the regex doesn't match, the closure will not be called and find will return null.
*
* For example, if the pattern doesn't match, the result is null:
*
* assert null == "New York, NY".find(~/\d{5}/) { match -> return "-$match-"}
*
*
* If it does match and we don't have any capture groups in our regex, there is a single parameter
* on the closure that the match gets passed to:
*
* assert "-10292-" == "New York, NY 10292-0098".find(~/\d{5}/) { match -> return "-$match-"}
*
*
* If we have capture groups in our expression, our closure has one parameter for the match, followed by
* one for each of the capture groups:
*
* assert "10292" == "New York, NY 10292-0098".find(~/(\d{5})-?(\d{4})/) { match, zip, plusFour ->
* assert match == "10292-0098"
* assert zip == "10292"
* assert plusFour == "0098"
* return zip
* }
*
* If we have capture groups in our expression, and our closure has one parameter,
* the closure will be passed an array with the first element corresponding to the whole match,
* followed by an element for each of the capture groups:
*
* assert "10292" == "New York, NY 10292-0098".find(~/(\d{5})-?(\d{4})/) { match, zip, plusFour ->
* assert array[0] == "10292-0098"
* assert array[1] == "10292"
* assert array[2] == "0098"
* return array[1]
* }
*
* If a capture group is optional, and doesn't match, then the corresponding value
* for that capture group passed to the closure will be null as illustrated here:
*
* assert "2339999" == "adsf 233-9999 adsf".find(~/(\d{3})?-?(\d{3})-(\d{4})/) { match, areaCode, exchange, stationNumber ->
* assert "233-9999" == match
* assert null == areaCode
* assert "233" == exchange
* assert "9999" == stationNumber
* return "$exchange$stationNumber"
* }
*
*
* @param self a String
* @param pattern the compiled regex Pattern
* @param closure the closure that will be passed the full match, plus each of the capturing groups
* @return a String containing the result of the closure, or null if the regex pattern doesn't match
* @since 1.6.1
*/
public static String find(String self, Pattern pattern, Closure closure) {
Matcher matcher = pattern.matcher(self);
if (matcher.find()) {
if (hasGroup(matcher)) {
int count = matcher.groupCount();
List groups = new ArrayList(count);
for (int i = 0; i <= count; i++) {
groups.add(matcher.group(i));
}
return InvokerHelper.toString(closure.call(groups));
} else {
return InvokerHelper.toString(closure.call(matcher.group(0)));
}
}
return null;
}
/**
* Finds the first occurrence of a regular expression String within a String.
* If the regex doesn't match, null will be returned.
*
* For example, if the regex doesn't match the result is null:
*
* assert null == "New York, NY".find(/\d{5}/)
*
*
* If it does match, we get the matching string back:
*
* assert "10292" == "New York, NY 10292-0098".find(/\d{5}/)
*
*
* If we have capture groups in our expression, we still get back the full match
*
* assert "10292-0098" == "New York, NY 10292-0098".find(/(\d{5})-?(\d{4})/)
*
*
* @param self a String
* @param regex the capturing regex
* @return a String containing the matched portion, or null if the regex doesn't match
* @since 1.6.1
*/
public static String find(String self, String regex) {
return find(self, Pattern.compile(regex));
}
/**
* Returns the result of calling a closure with the first occurrence of a regular expression found within a String.
* If the regex doesn't match, the closure will not be called and find will return null.
*
* For example, if the regex doesn't match, the result is null:
*
* assert null == "New York, NY".find(~/\d{5}/) { match -> return "-$match-"}
*
*
* If it does match and we don't have any capture groups in our regex, there is a single parameter
* on the closure that the match gets passed to:
*
* assert "-10292-" == "New York, NY 10292-0098".find(~/\d{5}/) { match -> return "-$match-"}
*
*
* If we have capture groups in our expression, our closure has one parameter for the match, followed by
* one for each of the capture groups:
*
* assert "10292" == "New York, NY 10292-0098".find(~/(\d{5})-?(\d{4})/) { match, zip, plusFour ->
* assert match == "10292-0098"
* assert zip == "10292"
* assert plusFour == "0098"
* return zip
* }
*
* If we have capture groups in our expression, and our closure has one parameter,
* the closure will be passed an array with the first element corresponding to the whole match,
* followed by an element for each of the capture groups:
*
* assert "10292" == "New York, NY 10292-0098".find(~/(\d{5})-?(\d{4})/) { match, zip, plusFour ->
* assert array[0] == "10292-0098"
* assert array[1] == "10292"
* assert array[2] == "0098"
* return array[1]
* }
*
* If a capture group is optional, and doesn't match, then the corresponding value
* for that capture group passed to the closure will be null as illustrated here:
*
* assert "2339999" == "adsf 233-9999 adsf".find(~/(\d{3})?-?(\d{3})-(\d{4})/) { match, areaCode, exchange, stationNumber ->
* assert "233-9999" == match
* assert null == areaCode
* assert "233" == exchange
* assert "9999" == stationNumber
* return "$exchange$stationNumber"
* }
*
*
* @param self a String
* @param regex the capturing regex string
* @param closure the closure that will be passed the full match, plus each of the capturing groups
* @return a String containing the result of the closure, or null if the regex pattern doesn't match
* @since 1.6.1
*/
public static String find(String self, String regex, Closure closure) {
return find(self, Pattern.compile(regex), closure);
}
/**
* Returns a (possibly empty) list of all occurrences of a regular expression (in CharSequence format) found within a CharSequence.
*
* @param self a CharSequence
* @param regex the capturing regex CharSequence
* @return a List containing all full matches of the regex within the CharSequence, an empty list will be returned if there are no matches
* @see #findAll(String, String)
* @since 1.8.2
*/
public static List findAll(CharSequence self, CharSequence regex) {
return new ArrayList(findAll(self.toString(), regex.toString()));
}
/**
* Finds all occurrences of a capturing regular expression CharSequence within a CharSequence.
*
* @param self a CharSequence
* @param regex the capturing regex CharSequence
* @param closure will be passed the full match plus each of the capturing groups
* @return a List containing all full matches of the regex within the CharSequence, an empty list will be returned if there are no matches
* @see #findAll(String, String, groovy.lang.Closure)
* @since 1.8.2
*/
public static List findAll(CharSequence self, CharSequence regex, Closure closure) {
return findAll(self.toString(), regex.toString(), closure);
}
/**
* Returns a (possibly empty) list of all occurrences of a regular expression (in Pattern format) found within a CharSequence.
*
* @param self a CharSequence
* @param pattern the compiled regex Pattern
* @return a List containing all full matches of the Pattern within the CharSequence, an empty list will be returned if there are no matches
* @see #findAll(String, java.util.regex.Pattern)
* @since 1.8.2
*/
public static List findAll(CharSequence self, Pattern pattern) {
return new ArrayList(findAll(self.toString(), pattern));
}
/**
* Finds all occurrences of a compiled regular expression Pattern within a CharSequence.
*
* @param self a CharSequence
* @param pattern the compiled regex Pattern
* @param closure will be passed the full match plus each of the capturing groups
* @return a List containing all full matches of the regex Pattern within the CharSequence, an empty list will be returned if there are no matches
* @see #findAll(String, java.util.regex.Pattern, groovy.lang.Closure)
* @since 1.8.2
*/
public static List findAll(CharSequence self, Pattern pattern, Closure closure) {
return findAll(self.toString(), pattern, closure);
}
/**
* Returns a (possibly empty) list of all occurrences of a regular expression (in Pattern format) found within a String.
*
* For example, if the pattern doesn't match, it returns an empty list:
*
* assert [] == "foo".findAll(~/(\w*) Fish/)
*
* Any regular expression matches are returned in a list, and all regex capture groupings are ignored, only the full match is returned:
*
* def expected = ["One Fish", "Two Fish", "Red Fish", "Blue Fish"]
* assert expected == "One Fish, Two Fish, Red Fish, Blue Fish".findAll(~/(\w*) Fish/)
*
*
* @param self a String
* @param pattern the compiled regex Pattern
* @return a List containing all full matches of the Pattern within the string, an empty list will be returned if there are no matches
* @since 1.6.1
*/
public static List findAll(String self, Pattern pattern) {
Matcher matcher = pattern.matcher(self);
List list = new ArrayList();
for (Iterator iter = iterator(matcher); iter.hasNext();) {
if (hasGroup(matcher)) {
list.add((String) ((List) iter.next()).get(0));
} else {
list.add((String) iter.next());
}
}
return list;
}
/**
* Finds all occurrences of a compiled regular expression Pattern within a String. Any matches are passed to the specified closure. The closure
* is expected to have the full match in the first parameter. If there are any capture groups, they will be placed in subsequent parameters.
*
* If there are no matches, the closure will not be called, and an empty List will be returned.
*
* For example, if the pattern doesn't match, it returns an empty list:
*
* assert [] == "foo".findAll(~/(\w*) Fish/) { match, firstWord -> return firstWord }
*
* Any regular expression matches are passed to the closure, if there are no capture groups, there will be one parameter for the match:
*
* assert ["couldn't", "wouldn't"] == "I could not, would not, with a fox.".findAll(~/.ould/) { match -> "${match}n't"}
*
* If there are capture groups, the first parameter will be the match followed by one parameter for each capture group:
*
* def orig = "There's a Wocket in my Pocket"
* assert ["W > Wocket", "P > Pocket"] == orig.findAll(~/(.)ocket/) { match, firstLetter -> "$firstLetter > $match" }
*
*
* @param self a String
* @param pattern the compiled regex Pattern
* @param closure will be passed the full match plus each of the capturing groups
* @return a List containing all full matches of the regex Pattern within the string, an empty list will be returned if there are no matches
* @since 1.6.1
*/
public static List findAll(String self, Pattern pattern, Closure closure) {
Matcher matcher = pattern.matcher(self);
return DefaultGroovyMethods.collect(matcher, closure);
}
/**
* Returns a (possibly empty) list of all occurrences of a regular expression (in String format) found within a String.
*
* For example, if the regex doesn't match, it returns an empty list:
*
* assert [] == "foo".findAll(/(\w*) Fish/)
*
* Any regular expression matches are returned in a list, and all regex capture groupings are ignored, only the full match is returned:
*
* def expected = ["One Fish", "Two Fish", "Red Fish", "Blue Fish"]
* assert expected == "One Fish, Two Fish, Red Fish, Blue Fish".findAll(/(\w*) Fish/)
*
* If you need to work with capture groups, then use the closure version
* of this method or use Groovy's matcher operators or use eachMatch.
*
* @param self a String
* @param regex the capturing regex String
* @return a List containing all full matches of the regex within the string, an empty list will be returned if there are no matches
* @since 1.6.1
*/
public static List findAll(String self, String regex) {
return findAll(self, Pattern.compile(regex));
}
/**
* Finds all occurrences of a regular expression string within a String. Any matches are passed to the specified closure. The closure
* is expected to have the full match in the first parameter. If there are any capture groups, they will be placed in subsequent parameters.
*
* If there are no matches, the closure will not be called, and an empty List will be returned.
*
* For example, if the regex doesn't match, it returns an empty list:
*
* assert [] == "foo".findAll(/(\w*) Fish/) { match, firstWord -> return firstWord }
*
* Any regular expression matches are passed to the closure, if there are no capture groups, there will be one parameter for the match:
*
* assert ["couldn't", "wouldn't"] == "I could not, would not, with a fox.".findAll(/.ould/) { match -> "${match}n't"}
*
* If there are capture groups, the first parameter will be the match followed by one parameter for each capture group:
*
* def orig = "There's a Wocket in my Pocket"
* assert ["W > Wocket", "P > Pocket"] == orig.findAll(/(.)ocket/) { match, firstLetter -> "$firstLetter > $match" }
*
*
* @param self a String
* @param regex the capturing regex String
* @param closure will be passed the full match plus each of the capturing groups
* @return a List containing all full matches of the regex within the string, an empty list will be returned if there are no matches
* @since 1.6.1
*/
public static List findAll(String self, String regex, Closure closure) {
return findAll(self, Pattern.compile(regex), closure);
}
// TODO expose this for stream based scenarios?
private static int findMinimumLeadingSpaces(String line, int count) {
int length = line.length();
int index = 0;
while (index < length && index < count && Character.isWhitespace(line.charAt(index))) index++;
return index;
}
/**
* Select a List of characters from a CharSequence using a Collection
* to identify the indices to be selected.
*
* @param self a CharSequence
* @param indices a Collection of indices
* @return a CharSequence consisting of the characters at the given indices
* @since 1.0
*/
public static CharSequence getAt(CharSequence self, Collection indices) {
StringBuilder answer = new StringBuilder();
for (Object value : indices) {
if (value instanceof Range) {
answer.append(getAt(self, (Range) value));
} else if (value instanceof Collection) {
answer.append(getAt(self, (Collection) value));
} else {
int idx = DefaultTypeTransformation.intUnbox(value);
answer.append(getAt(self, idx));
}
}
return answer.toString();
}
/**
* Support the range subscript operator for CharSequence or StringBuffer with EmptyRange
*
* @param text a CharSequence
* @param range an EmptyRange
* @return the subsequence CharSequence
* @since 1.5.0
*/
public static CharSequence getAt(CharSequence text, EmptyRange range) {
return "";
}
/**
* Support the subscript operator for CharSequence.
*
* @param text a CharSequence
* @param index the index of the Character to get
* @return the Character at the given index
* @since 1.0
*/
public static CharSequence getAt(CharSequence text, int index) {
index = normaliseIndex(index, text.length());
return text.subSequence(index, index + 1);
}
/**
* Support the range subscript operator for CharSequence or StringBuffer with IntRange
*
* @param text a CharSequence
* @param range an IntRange
* @return the subsequence CharSequence
* @since 1.0
*/
public static CharSequence getAt(CharSequence text, IntRange range) {
return getAt(text, (Range) range);
}
/**
* Support the range subscript operator for CharSequence
*
* @param text a CharSequence
* @param range a Range
* @return the subsequence CharSequence
* @since 1.0
*/
public static CharSequence getAt(CharSequence text, Range range) {
int from = normaliseIndex(DefaultTypeTransformation.intUnbox(range.getFrom()), text.length());
int to = normaliseIndex(DefaultTypeTransformation.intUnbox(range.getTo()), text.length());
boolean reverse = range.isReverse();
// If this is a backwards range, reverse the arguments to substring.
if (from > to) {
int tmp = from;
from = to;
to = tmp;
reverse = !reverse;
}
CharSequence sequence = text.subSequence(from, to + 1);
return reverse ? reverse((String) sequence) : sequence;
}
/**
* Select a List of values from a Matcher using a Collection
* to identify the indices to be selected.
*
* @param self a Matcher
* @param indices a Collection of indices
* @return a String of the values at the given indices
* @since 1.6.0
*/
public static List getAt(Matcher self, Collection indices) {
List result = new ArrayList();
for (Object value : indices) {
if (value instanceof Range) {
result.addAll(getAt(self, (Range) value));
} else {
int idx = DefaultTypeTransformation.intUnbox(value);
result.add(getAt(self, idx));
}
}
return result;
}
/**
* Support the subscript operator, e.g. matcher[index], for a regex Matcher.
*
* For an example using no group match,
*
* def p = /ab[d|f]/
* def m = "abcabdabeabf" =~ p
* assert 2 == m.count
* assert 2 == m.size() // synonym for m.getCount()
* assert ! m.hasGroup()
* assert 0 == m.groupCount()
* def matches = ["abd", "abf"]
* for (i in 0..<m.count) {
* assert m[i] == matches[i]
* }
*
*
* For an example using group matches,
*
* def p = /(?:ab([c|d|e|f]))/
* def m = "abcabdabeabf" =~ p
* assert 4 == m.count
* assert m.hasGroup()
* assert 1 == m.groupCount()
* def matches = [["abc", "c"], ["abd", "d"], ["abe", "e"], ["abf", "f"]]
* for (i in 0..<m.count) {
* assert m[i] == matches[i]
* }
*
*
* For another example using group matches,
*
* def m = "abcabdabeabfabxyzabx" =~ /(?:ab([d|x-z]+))/
* assert 3 == m.count
* assert m.hasGroup()
* assert 1 == m.groupCount()
* def matches = [["abd", "d"], ["abxyz", "xyz"], ["abx", "x"]]
* for (i in 0..<m.count) {
* assert m[i] == matches[i]
* }
*
*
* @param matcher a Matcher
* @param idx an index
* @return object a matched String if no groups matched, list of matched groups otherwise.
* @since 1.0
*/
public static Object getAt(Matcher matcher, int idx) {
try {
int count = getCount(matcher);
if (idx < -count || idx >= count) {
throw new IndexOutOfBoundsException("index is out of range " + (-count) + ".." + (count - 1) + " (index = " + idx + ")");
}
idx = normaliseIndex(idx, count);
Iterator iter = iterator(matcher);
Object result = null;
for (int i = 0; i <= idx; i++) {
result = iter.next();
}
return result;
}
catch (IllegalStateException ex) {
return null;
}
}
/**
* Given a matcher that matches a string against a pattern,
* this method returns true when the string matches the pattern or if a longer string, could match the pattern.
*
* For example:
*
* def emailPattern = /\w+@\w+\.\w{2,}/
*
* def matcher = "john@doe" =~ emailPattern
* assert matcher.matchesPartially()
*
* matcher = "[email protected]" =~ emailPattern
* assert matcher.matchesPartially()
*
* matcher = "john@@" =~ emailPattern
* assert !matcher.matchesPartially()
*
*
* @param matcher the Matcher
* @return true if more input to the String could make the matcher match the associated pattern, false otherwise.
*
* @since 2.0.0
*/
public static boolean matchesPartially(Matcher matcher) {
return matcher.matches() || matcher.hitEnd();
}
/**
* Select a List of characters from a String using a Collection
* to identify the indices to be selected.
*
* @param self a String
* @param indices a Collection of indices
* @return a String consisting of the characters at the given indices
* @since 1.0
*/
public static String getAt(String self, Collection indices) {
return (String) getAt((CharSequence) self, indices);
}
/**
* Support the range subscript operator for String with EmptyRange
*
* @param text a String
* @param range an EmptyRange
* @return the resulting String
* @since 1.5.0
*/
public static String getAt(String text, EmptyRange range) {
return "";
}
/**
* Support the subscript operator for String.
*
* @param text a String
* @param index the index of the Character to get
* @return the Character at the given index
* @since 1.0
*/
public static String getAt(String text, int index) {
index = normaliseIndex(index, text.length());
return text.substring(index, index + 1);
}
/**
* Support the range subscript operator for String with IntRange
*
* @param text a String
* @param range an IntRange
* @return the resulting String
* @since 1.0
*/
public static String getAt(String text, IntRange range) {
return getAt(text, (Range) range);
}
/**
* Support the range subscript operator for String
*
* @param text a String
* @param range a Range
* @return a substring corresponding to the Range
* @since 1.0
*/
public static String getAt(String text, Range range) {
int from = normaliseIndex(DefaultTypeTransformation.intUnbox(range.getFrom()), text.length());
int to = normaliseIndex(DefaultTypeTransformation.intUnbox(range.getTo()), text.length());
// If this is a backwards range, reverse the arguments to substring.
boolean reverse = range.isReverse();
if (from > to) {
int tmp = to;
to = from;
from = tmp;
reverse = !reverse;
}
String answer = text.substring(from, to + 1);
if (reverse) {
answer = reverse(answer);
}
return answer;
}
/**
* Converts the given CharSequence into an array of characters.
*
* @param self a CharSequence
* @return an array of characters
* @see #getChars(String)
* @since 1.8.2
*/
public static char[] getChars(CharSequence self) {
return getChars(self.toString());
}
/**
* Converts the given String into an array of characters.
* Alias for toCharArray.
*
* @param self a String
* @return an array of characters
* @see String#toCharArray()
* @since 1.6.0
*/
public static char[] getChars(String self) {
return self.toCharArray();
}
/**
* Find the number of Strings matched to the given Matcher.
*
* @param matcher a Matcher
* @return int the number of Strings matched to the given matcher.
* @since 1.0
*/
public static int getCount(Matcher matcher) {
int counter = 0;
matcher.reset();
while (matcher.find()) {
counter++;
}
return counter;
}
private static String getPadding(String padding, int length) {
if (padding.length() < length) {
return multiply(padding, length / padding.length() + 1).substring(0, length);
} else {
return padding.substring(0, length);
}
}
/**
* Get a replacement corresponding to the matched pattern for {@link org.codehaus.groovy.runtime.StringGroovyMethods#replaceAll(String, java.util.regex.Pattern, groovy.lang.Closure)}.
* The closure take parameter:
*
* - Whole of match if the pattern include no capturing group
* - Object[] of capturing groups if the closure takes Object[] as parameter
* - List of capturing groups
*
*
* @param matcher the matcher object used for matching
* @param closure specified with replaceAll() to get replacement
* @return replacement correspond replacement for a match
*/
private static String getReplacement(Matcher matcher, Closure closure) {
if (!hasGroup(matcher)) {
return InvokerHelper.toString(closure.call(matcher.group()));
}
int count = matcher.groupCount();
List groups = new ArrayList();
for (int i = 0; i <= count; i++) {
groups.add(matcher.group(i));
}
if (closure.getParameterTypes().length == 1
&& closure.getParameterTypes()[0] == Object[].class) {
return InvokerHelper.toString(closure.call(groups.toArray()));
}
return InvokerHelper.toString(closure.call(groups));
}
/**
* Check whether a Matcher contains a group or not.
*
* @param matcher a Matcher
* @return boolean true
if matcher contains at least one group.
* @since 1.0
*/
public static boolean hasGroup(Matcher matcher) {
return matcher.groupCount() > 0;
}
/**
* True if a CharSequence only contains whitespace characters.
*
* @param self The CharSequence to check the characters in
* @return true If all characters are whitespace characters
* @see #isAllWhitespace(String)
* @since 1.8.2
*/
public static boolean isAllWhitespace(CharSequence self) {
return isAllWhitespace(self.toString());
}
/**
* True if a String only contains whitespace characters.
*
* @param self The String to check the characters in
* @return true If all characters are whitespace characters
* @see Character#isWhitespace(char)
* @since 1.6
*/
public static boolean isAllWhitespace(String self) {
for (int i = 0; i < self.length(); i++) {
if (!Character.isWhitespace(self.charAt(i)))
return false;
}
return true;
}
/**
* Determine if a CharSequence can be parsed as a BigDecimal.
*
* @param self a CharSequence
* @return true if the CharSequence can be parsed
* @see #isBigDecimal(String)
* @since 1.8.2
*/
public static boolean isBigDecimal(CharSequence self) {
return isBigDecimal(self.toString());
}
/**
* Determine if a String can be parsed into a BigDecimal.
*
* @param self a String
* @return true if the string can be parsed
* @since 1.5.0
*/
public static boolean isBigDecimal(String self) {
try {
new BigDecimal(self.trim());
return true;
} catch (NumberFormatException nfe) {
return false;
}
}
/**
* Determine if a CharSequence can be parsed as a BigInteger.
*
* @param self a CharSequence
* @return true if the CharSequence can be parsed
* @see #isBigInteger(String)
* @since 1.8.2
*/
public static boolean isBigInteger(CharSequence self) {
return isBigInteger(self.toString());
}
/**
* Determine if a String can be parsed into a BigInteger.
*
* @param self a String
* @return true if the string can be parsed
* @since 1.5.0
*/
public static boolean isBigInteger(String self) {
try {
new BigInteger(self.trim());
return true;
} catch (NumberFormatException nfe) {
return false;
}
}
/**
* 'Case' implementation for a CharSequence, which simply calls the equivalent method for String.
*
* @param caseValue the case value
* @param switchValue the switch value
* @return true if the switchValue's toString() equals the caseValue
* @since 1.8.2
*/
public static boolean isCase(CharSequence caseValue, Object switchValue) {
return isCase(caseValue.toString(), switchValue);
}
/**
* 'Case' implementation for a GString, which simply calls the equivalent method for String.
*
* @param caseValue the case value
* @param switchValue the switch value
* @return true if the switchValue's toString() equals the caseValue
* @since 1.6.0
*/
public static boolean isCase(GString caseValue, Object switchValue) {
return isCase(caseValue.toString(), switchValue);
}
/**
* 'Case' implementation for the {@link java.util.regex.Pattern} class, which allows
* testing a String against a number of regular expressions.
* For example:
* switch( str ) {
* case ~/one/ :
* // the regex 'one' matches the value of str
* }
*
* Note that this returns true for the case where both the pattern and
* the 'switch' values are null
.
*
* @param caseValue the case value
* @param switchValue the switch value
* @return true if the switchValue is deemed to match the caseValue
* @since 1.0
*/
public static boolean isCase(Pattern caseValue, Object switchValue) {
if (switchValue == null) {
return caseValue == null;
}
final Matcher matcher = caseValue.matcher(switchValue.toString());
if (matcher.matches()) {
RegexSupport.setLastMatcher(matcher);
return true;
} else {
return false;
}
}
/**
* 'Case' implementation for a String, which uses String#equals(Object)
* in order to allow Strings to be used in switch statements.
* For example:
* switch( str ) {
* case 'one' :
* // etc...
* }
* Note that this returns true
for the case where both the
* 'switch' and 'case' operand is null
.
*
* @param caseValue the case value
* @param switchValue the switch value
* @return true if the switchValue's toString() equals the caseValue
* @since 1.0
*/
public static boolean isCase(String caseValue, Object switchValue) {
if (switchValue == null) {
return caseValue == null;
}
return caseValue.equals(switchValue.toString());
}
/**
* Determine if a CharSequence can be parsed as a Double.
*
* @param self a CharSequence
* @return true if the CharSequence can be parsed
* @see #isDouble(String)
* @since 1.8.2
*/
public static boolean isDouble(CharSequence self) {
return isDouble(self.toString());
}
/**
* Determine if a String can be parsed into a Double.
*
* @param self a String
* @return true if the string can be parsed
* @since 1.5.0
*/
public static boolean isDouble(String self) {
try {
Double.valueOf(self.trim());
return true;
} catch (NumberFormatException nfe) {
return false;
}
}
/**
* Determine if a CharSequence can be parsed as a Float.
*
* @param self a CharSequence
* @return true if the CharSequence can be parsed
* @see #isFloat(String)
* @since 1.8.2
*/
public static boolean isFloat(CharSequence self) {
return isFloat(self.toString());
}
/**
* Determine if a String can be parsed into a Float.
*
* @param self a String
* @return true if the string can be parsed
* @since 1.5.0
*/
public static boolean isFloat(String self) {
try {
Float.valueOf(self.trim());
return true;
} catch (NumberFormatException nfe) {
return false;
}
}
/**
* Determine if a CharSequence can be parsed as an Integer.
*
* @param self a CharSequence
* @return true if the CharSequence can be parsed
* @see #isInteger(String)
* @since 1.8.2
*/
public static boolean isInteger(CharSequence self) {
return isInteger(self.toString());
}
/**
* Determine if a String can be parsed into an Integer.
*
* @param self a String
* @return true if the string can be parsed
* @since 1.5.0
*/
public static boolean isInteger(String self) {
try {
Integer.valueOf(self.trim());
return true;
} catch (NumberFormatException nfe) {
return false;
}
}
/**
* Determine if a CharSequence can be parsed as a Long.
*
* @param self a CharSequence
* @return true if the CharSequence can be parsed
* @see #isLong(String)
* @since 1.8.2
*/
public static boolean isLong(CharSequence self) {
return isLong(self.toString());
}
/**
* Determine if a String can be parsed into a Long.
*
* @param self a String
* @return true if the string can be parsed
* @since 1.5.0
*/
public static boolean isLong(String self) {
try {
Long.valueOf(self.trim());
return true;
} catch (NumberFormatException nfe) {
return false;
}
}
/**
* Determine if a CharSequence can be parsed as a Number.
* Synonym for 'isBigDecimal()'.
*
* @param self a CharSequence
* @return true if the CharSequence can be parsed
* @see #isNumber(String)
* @since 1.8.2
*/
public static boolean isNumber(CharSequence self) {
return isNumber(self.toString());
}
/**
* Determine if a String can be parsed into a Number.
* Synonym for 'isBigDecimal()'.
*
* @param self a String
* @return true if the string can be parsed
* @see #isBigDecimal(String)
* @since 1.5.0
*/
public static boolean isNumber(String self) {
return isBigDecimal(self);
}
/**
* Returns an {@link java.util.Iterator} which traverses each match.
*
* @param matcher a Matcher object
* @return an Iterator for a Matcher
* @see java.util.regex.Matcher#group()
* @since 1.0
*/
public static Iterator iterator(final Matcher matcher) {
matcher.reset();
return new Iterator() {
private boolean found /* = false */;
private boolean done /* = false */;
public boolean hasNext() {
if (done) {
return false;
}
if (!found) {
found = matcher.find();
if (!found) {
done = true;
}
}
return found;
}
public Object next() {
if (!found) {
if (!hasNext()) {
throw new NoSuchElementException();
}
}
found = false;
if (hasGroup(matcher)) {
// are we using groups?
// yes, so return the specified group as list
List list = new ArrayList(matcher.groupCount());
for (int i = 0; i <= matcher.groupCount(); i++) {
list.add(matcher.group(i));
}
return list;
} else {
// not using groups, so return the nth
// occurrence of the pattern
return matcher.group();
}
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* Overloads the left shift operator to provide an easy way to append multiple
* objects as string representations to a CharSequence.
*
* @param self a CharSequence
* @param value an Object
* @return a StringBuilder built from this CharSequence
* @since 1.8.2
*/
public static StringBuilder leftShift(CharSequence self, Object value) {
return new StringBuilder(self).append(value);
}
/**
* Overloads the left shift operator to provide an easy way to append multiple
* objects as string representations to a String.
*
* @param self a String
* @param value an Object
* @return a StringBuffer built from this string
* @since 1.0
*/
public static StringBuffer leftShift(String self, Object value) {
return new StringBuffer(self).append(value);
}
/**
* Overloads the left shift operator to provide an easy way to append multiple
* objects as string representations to a StringBuffer.
*
* @param self a StringBuffer
* @param value a value to append
* @return the StringBuffer on which this operation was invoked
* @since 1.0
*/
public static StringBuffer leftShift(StringBuffer self, Object value) {
self.append(value);
return self;
}
/**
* Overloads the left shift operator to provide syntactic sugar for appending to a StringBuilder.
*
* @param self a StringBuilder
* @param value an Object
* @return the original StringBuilder
* @since 1.8.2
*/
public static StringBuilder leftShift(StringBuilder self, Object value) {
self.append(value);
return self;
}
/**
* Tells whether or not a CharSequence matches the given
* compiled regular expression Pattern.
*
* @param self the CharSequence that is to be matched
* @param pattern the regex Pattern to which the string of interest is to be matched
* @return true if the CharSequence matches
* @see String#matches(String)
* @since 1.8.2
*/
public static boolean matches(CharSequence self, Pattern pattern) {
return pattern.matcher(self).matches();
}
/**
* Tells whether or not self matches the given
* compiled regular expression Pattern.
*
* @param self the string that is to be matched
* @param pattern the regex Pattern to which the string of interest is to be matched
* @return true if the string matches
* @see String#matches(String)
* @since 1.6.1
*/
public static boolean matches(String self, Pattern pattern) {
return pattern.matcher(self).matches();
}
/**
* Remove a part of a CharSequence by replacing the first occurrence
* of target within self with '' and returns the result.
*
* @param self a CharSequence
* @param target an object representing the part to remove
* @return a CharSequence minus the part to be removed
* @see #minus(String, Object)
* @since 1.8.2
*/
public static CharSequence minus(CharSequence self, Object target) {
return minus(self.toString(), target);
}
/**
* Remove a part of a String. This replaces the first occurrence
* of target within self with '' and returns the result. If
* target is a regex Pattern, the first occurrence of that
* pattern will be removed (using regex matching), otherwise
* the first occurrence of target.toString() will be removed.
*
* @param self a String
* @param target an object representing the part to remove
* @return a String minus the part to be removed
* @since 1.0
*/
public static String minus(String self, Object target) {
if (target instanceof Pattern) {
return ((Pattern)target).matcher(self).replaceFirst("");
}
String text = DefaultGroovyMethods.toString(target);
int index = self.indexOf(text);
if (index == -1) return self;
int end = index + text.length();
if (self.length() > end) {
return self.substring(0, index) + self.substring(end);
}
return self.substring(0, index);
}
/**
* Repeat a CharSequence a certain number of times.
*
* @param self a CharSequence to be repeated
* @param factor the number of times the CharSequence should be repeated
* @return a CharSequence composed of a repetition
* @throws IllegalArgumentException if the number of repetitions is < 0
* @since 1.8.2
*/
public static CharSequence multiply(CharSequence self, Number factor) {
return multiply(self.toString(), factor);
}
/**
* Repeat a String a certain number of times.
*
* @param self a String to be repeated
* @param factor the number of times the String should be repeated
* @return a String composed of a repetition
* @throws IllegalArgumentException if the number of repetitions is < 0
* @since 1.0
*/
public static String multiply(String self, Number factor) {
int size = factor.intValue();
if (size == 0)
return "";
else if (size < 0) {
throw new IllegalArgumentException("multiply() should be called with a number of 0 or greater not: " + size);
}
StringBuilder answer = new StringBuilder(self);
for (int i = 1; i < size; i++) {
answer.append(self);
}
return answer.toString();
}
/**
* This method is called by the ++ operator for the class CharSequence.
*
* @param self a CharSequence
* @return an incremented CharSequence
* @see #next(String)
* @since 1.8.2
*/
public static CharSequence next(CharSequence self) {
return next(self.toString());
}
/**
* This method is called by the ++ operator for the class String.
* It increments the last character in the given string. If the
* character in the string is Character.MAX_VALUE a Character.MIN_VALUE
* will be appended. The empty string is incremented to a string
* consisting of the character Character.MIN_VALUE.
*
* @param self a String
* @return an incremented String
* @since 1.0
*/
public static String next(String self) {
StringBuilder buffer = new StringBuilder(self);
if (buffer.length() == 0) {
buffer.append(Character.MIN_VALUE);
} else {
char last = buffer.charAt(buffer.length() - 1);
if (last == Character.MAX_VALUE) {
buffer.append(Character.MIN_VALUE);
} else {
char next = last;
next++;
buffer.setCharAt(buffer.length() - 1, next);
}
}
return buffer.toString();
}
/**
* Return a CharSequence with linefeeds and carriage returns normalized to linefeeds.
*
* @param self a CharSequence object
* @return the normalized CharSequence
* @see #normalize(String)
* @since 1.8.2
*/
public static CharSequence normalize(final CharSequence self) {
return normalize(self.toString());
}
/**
* Return a String with linefeeds and carriage returns normalized to linefeeds.
*
* @param self a String object
* @return the normalized string
* @since 1.6.0
*/
public static String normalize(final String self) {
int nx = self.indexOf('\r');
if (nx < 0) {
return self;
}
final int len = self.length();
final StringBuilder sb = new StringBuilder(len);
int i = 0;
do {
sb.append(self, i, nx);
sb.append('\n');
if ((i = nx + 1) >= len) break;
if (self.charAt(i) == '\n') {
// skip the LF in CR LF
if (++i >= len) break;
}
nx = self.indexOf('\r', i);
} while (nx > 0);
sb.append(self, i, len);
return sb.toString();
}
/**
* Pad a CharSequence to a minimum length specified by numberOfChars by adding the space character to the left as many times as needed.
*
* @param self a CharSequence object
* @param numberOfChars the total minimum number of characters of the resulting CharSequence
* @return the CharSequence padded to the left
* @see #padLeft(CharSequence, Number, CharSequence)
* @since 1.8.2
*/
public static CharSequence padLeft(CharSequence self, Number numberOfChars) {
return padLeft(self, numberOfChars, " ");
}
/**
* Pad a CharSequence to a minimum length specified by numberOfChars, adding the supplied padding CharSequence as many times as needed to the left.
*
* @param self a CharSequence object
* @param numberOfChars the total minimum number of characters of the resulting CharSequence
* @param padding the characters used for padding
* @return the CharSequence padded to the left
* @see #padLeft(String, Number, String)
* @since 1.8.2
*/
public static CharSequence padLeft(CharSequence self, Number numberOfChars, CharSequence padding) {
return padLeft(self.toString(), numberOfChars, padding.toString());
}
/**
* Pad a String to a minimum length specified by numberOfChars by adding the space character to the left as many times as needed.
*
* If the String is already the same size or bigger than the target numberOfChars, then the original String is returned. An example:
*
* println 'Numbers:'
* [1, 10, 100, 1000].each{ println it.toString().padLeft(5) }
*
* will produce output like:
*
* Numbers:
* 1
* 10
* 100
* 1000
*
*
* @param self a String object
* @param numberOfChars the total minimum number of characters of the resulting string
* @return the String padded to the left
* @see #padLeft(String, Number, String)
* @since 1.0
*/
public static String padLeft(String self, Number numberOfChars) {
return padLeft(self, numberOfChars, " ");
}
/**
* Pad a String to a minimum length specified by numberOfChars, adding the supplied padding String as many times as needed to the left.
*
* If the String is already the same size or bigger than the target numberOfChars, then the original String is returned. An example:
*
* println 'Numbers:'
* [1, 10, 100, 1000].each{ println it.toString().padLeft(5, '*') }
* [2, 20, 200, 2000].each{ println it.toString().padLeft(5, '*_') }
*
* will produce output like:
*
* Numbers:
* ****1
* ***10
* **100
* *1000
* *_*_2
* *_*20
* *_200
* *2000
*
*
* @param self a String object
* @param numberOfChars the total minimum number of characters of the resulting string
* @param padding the characters used for padding
* @return the String padded to the left
* @since 1.0
*/
public static String padLeft(String self, Number numberOfChars, String padding) {
int numChars = numberOfChars.intValue();
if (numChars <= self.length()) {
return self;
} else {
return getPadding(padding, numChars - self.length()) + self;
}
}
/**
* Pad a CharSequence to a minimum length specified by numberOfChars by adding the space character to the right as many times as needed.
*
* @param self a CharSequence object
* @param numberOfChars the total minimum number of characters of the resulting string
* @return the CharSequence padded to the right
* @see #padRight(String, Number)
* @since 1.8.2
*/
public static CharSequence padRight(CharSequence self, Number numberOfChars) {
return padRight(self.toString(), numberOfChars);
}
/**
* Pad a CharSequence to a minimum length specified by numberOfChars, adding the supplied padding CharSequence as many times as needed to the right.
*
* @param self a CharSequence object
* @param numberOfChars the total minimum number of characters of the resulting CharSequence
* @param padding the characters used for padding
* @return the CharSequence padded to the right
* @see #padRight(String, Number, String)
* @since 1.8.2
*/
public static CharSequence padRight(CharSequence self, Number numberOfChars, CharSequence padding) {
return padRight(self.toString(), numberOfChars, padding.toString());
}
/**
* Pad a String to a minimum length specified by numberOfChars by adding the space character to the right as many times as needed.
*
* If the String is already the same size or bigger than the target numberOfChars, then the original String is returned. An example:
*
* ['A', 'BB', 'CCC', 'DDDD'].each{ println it.padRight(5) + it.size() }
*
* will produce output like:
*
* A 1
* BB 2
* CCC 3
* DDDD 4
*
*
* @param self a String object
* @param numberOfChars the total minimum number of characters of the resulting string
* @return the String padded to the right
* @since 1.0
*/
public static String padRight(String self, Number numberOfChars) {
return padRight(self, numberOfChars, " ");
}
/**
* Pad a String to a minimum length specified by numberOfChars, adding the supplied padding String as many times as needed to the right.
*
* If the String is already the same size or bigger than the target numberOfChars, then the original String is returned. An example:
*
* ['A', 'BB', 'CCC', 'DDDD'].each{ println it.padRight(5, '#') + it.size() }
*
* will produce output like:
*
* A####1
* BB###2
* CCC##3
* DDDD#4
*
*
* @param self a String object
* @param numberOfChars the total minimum number of characters of the resulting string
* @param padding the characters used for padding
* @return the String padded to the right
* @since 1.0
*/
public static String padRight(String self, Number numberOfChars, String padding) {
int numChars = numberOfChars.intValue();
if (numChars <= self.length()) {
return self;
} else {
return self + getPadding(padding, numChars - self.length());
}
}
/**
* Appends the String representation of the given operand to this string.
*
* @param left a CharSequence
* @param value any Object
* @return the new CharSequence with the object appended
* @since 1.8.2
*/
public static CharSequence plus(CharSequence left, Object value) {
return left + DefaultGroovyMethods.toString(value);
}
/**
* Appends a String to the string representation of this number.
*
* @param value a Number
* @param right a String
* @return a String
* @since 1.0
*/
public static String plus(Number value, String right) {
return DefaultGroovyMethods.toString(value) + right;
}
/**
* Appends the String representation of the given operand to this string.
*
* @param left a String
* @param value any Object
* @return the new string with the object appended
* @since 1.0
*/
public static String plus(String left, Object value) {
return left + DefaultGroovyMethods.toString(value);
}
/**
* Appends a String to this StringBuffer.
*
* @param left a StringBuffer
* @param value a String
* @return a String
* @since 1.0
*/
public static String plus(StringBuffer left, String value) {
return left + value;
}
/**
* This method is called by the -- operator for the class CharSequence.
*
* @param self a CharSequence
* @return a CharSequence with a decremented digit at the end
* @see #previous(String)
* @since 1.8.2
*/
public static CharSequence previous(CharSequence self) {
return previous(self.toString());
}
/**
* This method is called by the -- operator for the class String.
* It decrements the last character in the given string. If the
* character in the string is Character.MIN_VALUE it will be deleted.
* The empty string can't be decremented.
*
* @param self a String
* @return a String with a decremented digit at the end
* @since 1.0
*/
public static String previous(String self) {
StringBuilder buffer = new StringBuilder(self);
if (buffer.length() == 0) throw new IllegalArgumentException("the string is empty");
char last = buffer.charAt(buffer.length() - 1);
if (last == Character.MIN_VALUE) {
buffer.deleteCharAt(buffer.length() - 1);
} else {
char next = last;
next--;
buffer.setCharAt(buffer.length() - 1, next);
}
return buffer.toString();
}
/**
* Support the range subscript operator for StringBuffer.
*
* @param self a StringBuffer
* @param range a Range
* @param value the object that's toString() will be inserted
* @since 1.0
*/
public static void putAt(StringBuffer self, EmptyRange range, Object value) {
RangeInfo info = subListBorders(self.length(), range);
self.replace(info.from, info.to, value.toString());
}
/**
* Support the range subscript operator for StringBuffer. Index values are
* treated as characters within the buffer.
*
* @param self a StringBuffer
* @param range a Range
* @param value the object that's toString() will be inserted
* @since 1.0
*/
public static void putAt(StringBuffer self, IntRange range, Object value) {
RangeInfo info = subListBorders(self.length(), range);
self.replace(info.from, info.to, value.toString());
}
/**
* Return the lines of a CharSequence as a List of CharSequence.
*
* @param self a CharSequence object
* @return a list of lines
* @throws java.io.IOException if an error occurs
* @since 1.8.2
*/
public static List readLines(CharSequence self) throws IOException {
return new ArrayList(readLines(self.toString()));
}
/**
* Return the lines of a String as a List of Strings.
*
* @param self a String object
* @return a list of lines
* @throws java.io.IOException if an error occurs
* @since 1.5.5
*/
public static List readLines(String self) throws IOException {
return IOGroovyMethods.readLines(new StringReader(self));
}
/**
* Replaces each substring of this CharSequence that matches the given
* regular expression with the given replacement.
*
* @param self a CharSequence
* @param regex the capturing regex
* @param replacement the capturing regex
* @return a CharSequence with replaced content
* @throws java.util.regex.PatternSyntaxException if the regular expression's syntax is invalid
* @see String#replaceAll(String, String)
* @since 1.8.2
*/
public static CharSequence replaceAll(final CharSequence self, final CharSequence regex, final CharSequence replacement) {
return self.toString().replaceAll(regex.toString(), replacement.toString());
}
/**
* Replaces all occurrences of a captured group by the result of a closure on that text.
*
* @param self a CharSequence
* @param regex the capturing regex
* @param closure the closure to apply on each captured group
* @return a CharSequence with replaced content
* @throws java.util.regex.PatternSyntaxException if the regular expression's syntax is invalid
* @since 1.8.2
* @see #replaceAll(String, java.util.regex.Pattern, groovy.lang.Closure)
*/
public static CharSequence replaceAll(final CharSequence self, final CharSequence regex, final Closure closure) {
return replaceAll(self.toString(), Pattern.compile(regex.toString()), closure);
}
/**
* Replaces all substrings of a CharSequence that match the given
* compiled regular expression with the given replacement.
*
* @param self the CharSequence that is to be matched
* @param pattern the regex Pattern to which the CharSequence of interest is to be matched
* @param replacement the CharSequence to be substituted for the first match
* @return The resulting CharSequence
* @see #replaceAll(String, java.util.regex.Pattern, String)
* @since 1.8.2
*/
public static CharSequence replaceAll(CharSequence self, Pattern pattern, CharSequence replacement) {
return pattern.matcher(self).replaceAll(replacement.toString());
}
/**
* Replaces all occurrences of a captured group by the result of a closure call on that text.
*
* @param self a CharSequence
* @param pattern the capturing regex Pattern
* @param closure the closure to apply on each captured group
* @return a CharSequence with replaced content
* @since 1.8.2
* @see #replaceAll(String, java.util.regex.Pattern, groovy.lang.Closure)
*/
public static String replaceAll(final CharSequence self, final Pattern pattern, final Closure closure) {
return replaceAll(self.toString(), pattern, closure);
}
/**
* Replaces all occurrences of a captured group by the result of a closure call on that text.
*
* For examples,
*
* assert "hellO wOrld" == "hello world".replaceAll(~"(o)") { it[0].toUpperCase() }
*
* assert "FOOBAR-FOOBAR-" == "foobar-FooBar-".replaceAll(~"(([fF][oO]{2})[bB]ar)", { it[0].toUpperCase() })
*
* Here,
* it[0] is the global string of the matched group
* it[1] is the first string in the matched group
* it[2] is the second string in the matched group
*
* assert "FOOBAR-FOOBAR-" == "foobar-FooBar-".replaceAll(~"(([fF][oO]{2})[bB]ar)", { Object[] it -> it[0].toUpperCase() })
*
* Here,
* it[0] is the global string of the matched group
* it[1] is the first string in the matched group
* it[2] is the second string in the matched group
*
* assert "FOO-FOO-" == "foobar-FooBar-".replaceAll("(([fF][oO]{2})[bB]ar)", { x, y, z -> z.toUpperCase() })
*
* Here,
* x is the global string of the matched group
* y is the first string in the matched group
* z is the second string in the matched group
*
* Note that unlike String.replaceAll(String regex, String replacement), where the replacement string
* treats '$' and '\' specially (for group substitution), the result of the closure is converted to a string
* and that value is used literally for the replacement.
*
* @param self a String
* @param pattern the capturing regex Pattern
* @param closure the closure to apply on each captured group
* @return a String with replaced content
* @since 1.6.8
* @see java.util.regex.Matcher#quoteReplacement(String)
*/
public static String replaceAll(final String self, final Pattern pattern, final Closure closure) {
final Matcher matcher = pattern.matcher(self);
if (matcher.find()) {
final StringBuffer sb = new StringBuffer(self.length() + 16);
do {
String replacement = getReplacement(matcher, closure);
matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
} while (matcher.find());
matcher.appendTail(sb);
return sb.toString();
} else {
return self;
}
}
/**
* Replaces all substrings of a String that match the given
* compiled regular expression with the given replacement.
*
* Note that backslashes (\) and dollar signs ($) in the
* replacement string may cause the results to be different than if it were
* being treated as a literal replacement string; see
* {@link java.util.regex.Matcher#replaceAll}.
* Use {@link java.util.regex.Matcher#quoteReplacement} to suppress the special
* meaning of these characters, if desired.
*
*
* assert "foo".replaceAll('o', 'X') == 'fXX'
*
*
* @param self the string that is to be matched
* @param pattern the regex Pattern to which the string of interest is to be matched
* @param replacement the string to be substituted for the first match
* @return The resulting String
* @see String#replaceAll(String, String)
* @since 1.6.1
*/
public static String replaceAll(String self, Pattern pattern, String replacement) {
return pattern.matcher(self).replaceAll(replacement);
}
/**
* Replaces all occurrences of a captured group by the result of a closure on that text.
*
* For examples,
*
* assert "hellO wOrld" == "hello world".replaceAll("(o)") { it[0].toUpperCase() }
*
* assert "FOOBAR-FOOBAR-" == "foobar-FooBar-".replaceAll("(([fF][oO]{2})[bB]ar)", { Object[] it -> it[0].toUpperCase() })
*
* Here,
* it[0] is the global string of the matched group
* it[1] is the first string in the matched group
* it[2] is the second string in the matched group
*
* assert "FOO-FOO-" == "foobar-FooBar-".replaceAll("(([fF][oO]{2})[bB]ar)", { x, y, z -> z.toUpperCase() })
*
* Here,
* x is the global string of the matched group
* y is the first string in the matched group
* z is the second string in the matched group
*
* Note that unlike String.replaceAll(String regex, String replacement), where the replacement string
* treats '$' and '\' specially (for group substitution), the result of the closure is converted to a string
* and that value is used literally for the replacement.
*
* @param self a String
* @param regex the capturing regex
* @param closure the closure to apply on each captured group
* @return a String with replaced content
* @throws java.util.regex.PatternSyntaxException if the regular expression's syntax is invalid
* @since 1.0
* @see java.util.regex.Matcher#quoteReplacement(String)
* @see #replaceAll(String, java.util.regex.Pattern, groovy.lang.Closure)
*/
public static String replaceAll(final String self, final String regex, final Closure closure) {
return replaceAll(self, Pattern.compile(regex), closure);
}
/**
* Replaces the first substring of this CharSequence that matches the given
* regular expression with the given replacement.
*
* @param self a CharSequence
* @param regex the capturing regex
* @param replacement the capturing regex
* @return a CharSequence with replaced content
* @throws java.util.regex.PatternSyntaxException if the regular expression's syntax is invalid
* @see String#replaceAll(String, String)
* @since 1.8.2
*/
public static String replaceFirst(final CharSequence self, final CharSequence regex, final CharSequence replacement) {
return self.toString().replaceFirst(regex.toString(), replacement.toString());
}
/**
* Replaces the first occurrence of a captured group by the result of a closure call on that text.
*
* @param self a CharSequence
* @param regex the capturing regex
* @param closure the closure to apply on the first captured group
* @return a CharSequence with replaced content
* @throws java.util.regex.PatternSyntaxException if the regular expression's syntax is invalid
* @see #replaceFirst(String, String, groovy.lang.Closure)
* @since 1.8.2
*/
public static String replaceFirst(final CharSequence self, final CharSequence regex, final Closure closure) {
return replaceFirst(self.toString(), regex.toString(), closure);
}
/**
* Replaces the first substring of a CharSequence that matches the given
* compiled regular expression with the given replacement.
*
* @param self the CharSequence that is to be matched
* @param pattern the regex Pattern to which the CharSequence of interest is to be matched
* @param replacement the CharSequence to be substituted for the first match
* @return The resulting CharSequence
* @see #replaceFirst(String, java.util.regex.Pattern, String)
* @since 1.8.2
*/
public static CharSequence replaceFirst(CharSequence self, Pattern pattern, CharSequence replacement) {
return pattern.matcher(self).replaceFirst(replacement.toString());
}
/**
* Replaces the first occurrence of a captured group by the result of a closure call on that text.
*
* @param self a CharSequence
* @param pattern the capturing regex Pattern
* @param closure the closure to apply on the first captured group
* @return a CharSequence with replaced content
* @see #replaceFirst(String, java.util.regex.Pattern, groovy.lang.Closure)
* @since 1.8.2
*/
public static String replaceFirst(final CharSequence self, final Pattern pattern, final Closure closure) {
return replaceFirst(self.toString(), pattern, closure);
}
/**
* Replaces the first occurrence of a captured group by the result of a closure call on that text.
*
* For example (with some replaceAll variants thrown in for comparison purposes),
*
* assert "hellO world" == "hello world".replaceFirst(~"(o)") { it[0].toUpperCase() } // first match
* assert "hellO wOrld" == "hello world".replaceAll(~"(o)") { it[0].toUpperCase() } // all matches
*
* assert '1-FISH, two fish' == "one fish, two fish".replaceFirst(~/([a-z]{3})\s([a-z]{4})/) { [one:1, two:2][it[1]] + '-' + it[2].toUpperCase() }
* assert '1-FISH, 2-FISH' == "one fish, two fish".replaceAll(~/([a-z]{3})\s([a-z]{4})/) { [one:1, two:2][it[1]] + '-' + it[2].toUpperCase() }
*
*
* @param self a String
* @param pattern the capturing regex Pattern
* @param closure the closure to apply on the first captured group
* @return a String with replaced content
* @since 1.7.7
* @see #replaceAll(String, java.util.regex.Pattern, groovy.lang.Closure)
*/
public static String replaceFirst(final String self, final Pattern pattern, final Closure closure) {
final Matcher matcher = pattern.matcher(self);
if (matcher.find()) {
final StringBuffer sb = new StringBuffer(self.length() + 16);
String replacement = getReplacement(matcher, closure);
matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
matcher.appendTail(sb);
return sb.toString();
} else {
return self;
}
}
/**
* Replaces the first substring of a String that matches the given
* compiled regular expression with the given replacement.
*
* Note that backslashes (\) and dollar signs ($) in the
* replacement string may cause the results to be different than if it were
* being treated as a literal replacement string; see
* {@link java.util.regex.Matcher#replaceFirst}.
* Use {@link java.util.regex.Matcher#quoteReplacement} to suppress the special
* meaning of these characters, if desired.
*
*
* assert "foo".replaceFirst('o', 'X') == 'fXo'
*
*
* @param self the string that is to be matched
* @param pattern the regex Pattern to which the string of interest is to be matched
* @param replacement the string to be substituted for the first match
* @return The resulting String
* @see String#replaceFirst(String, String)
* @since 1.6.1
*/
public static String replaceFirst(String self, Pattern pattern, String replacement) {
return pattern.matcher(self).replaceFirst(replacement);
}
/**
* Replaces the first occurrence of a captured group by the result of a closure call on that text.
*
* For example (with some replaceAll variants thrown in for comparison purposes),
*
* assert "hellO world" == "hello world".replaceFirst("(o)") { it[0].toUpperCase() } // first match
* assert "hellO wOrld" == "hello world".replaceAll("(o)") { it[0].toUpperCase() } // all matches
*
* assert '1-FISH, two fish' == "one fish, two fish".replaceFirst(/([a-z]{3})\s([a-z]{4})/) { [one:1, two:2][it[1]] + '-' + it[2].toUpperCase() }
* assert '1-FISH, 2-FISH' == "one fish, two fish".replaceAll(/([a-z]{3})\s([a-z]{4})/) { [one:1, two:2][it[1]] + '-' + it[2].toUpperCase() }
*
*
* @param self a String
* @param regex the capturing regex
* @param closure the closure to apply on the first captured group
* @return a String with replaced content
* @throws java.util.regex.PatternSyntaxException if the regular expression's syntax is invalid
* @since 1.7.7
* @see java.util.regex.Matcher#quoteReplacement(String)
* @see #replaceFirst(String, java.util.regex.Pattern, groovy.lang.Closure)
*/
public static String replaceFirst(final String self, final String regex, final Closure closure) {
return replaceFirst(self, Pattern.compile(regex), closure);
}
/**
* Creates a new CharSequence which is the reverse (backwards) of this string
*
* @param self a CharSequence
* @return a new CharSequence with all the characters reversed.
* @see #reverse(String)
* @since 1.8.2
*/
public static CharSequence reverse(CharSequence self) {
return new StringBuilder(self).reverse().toString();
}
/**
* Creates a new string which is the reverse (backwards) of this string
*
* @param self a String
* @return a new string with all the characters reversed.
* @since 1.0
* @see StringBuilder#reverse()
*/
public static String reverse(String self) {
return new StringBuilder(self).reverse().toString();
}
/**
* Set the position of the given Matcher to the given index.
*
* @param matcher a Matcher
* @param idx the index number
* @since 1.0
*/
public static void setIndex(Matcher matcher, int idx) {
int count = getCount(matcher);
if (idx < -count || idx >= count) {
throw new IndexOutOfBoundsException("index is out of range " + (-count) + ".." + (count - 1) + " (index = " + idx + ")");
}
if (idx == 0) {
matcher.reset();
} else if (idx > 0) {
matcher.reset();
for (int i = 0; i < idx; i++) {
matcher.find();
}
} else if (idx < 0) {
matcher.reset();
idx += getCount(matcher);
for (int i = 0; i < idx; i++) {
matcher.find();
}
}
}
/**
* Provide the standard Groovy size()
method for CharSequence
.
*
* @param text a CharSequence
* @return the length of the CharSequence
* @since 1.8.2
*/
public static int size(CharSequence text) {
return text.length();
}
/**
* Provide the standard Groovy size()
method for Matcher
.
*
* @param self a matcher object
* @return the matcher's size (count)
* @since 1.5.0
*/
public static long size(Matcher self) {
return getCount(self);
}
/**
* Provide the standard Groovy size()
method for String
.
*
* @param text a String
* @return the length of the String
* @since 1.0
*/
public static int size(String text) {
return text.length();
}
/**
* Provide the standard Groovy size()
method for StringBuffer
.
*
* @param buffer a StringBuffer
* @return the length of the StringBuffer
* @since 1.0
*/
public static int size(StringBuffer buffer) {
return buffer.length();
}
/**
* Convenience method to split a CharSequence (with whitespace as delimiter).
* Similar to tokenize, but returns an Array of CharSequence instead of a List.
*
* @param self the CharSequence to split
* @return CharSequence[] result of split
* @see #split(String)
* @since 1.8.2
*/
public static CharSequence[] split(CharSequence self) {
return split(self.toString());
}
/**
* Convenience method to split a GString (with whitespace as delimiter).
*
* @param self the GString to split
* @return String[] result of split
* @see #split(String)
* @since 1.6.1
*/
public static String[] split(GString self) {
return split(self.toString());
}
/**
* Convenience method to split a string (with whitespace as delimiter)
* Like tokenize, but returns an Array of Strings instead of a List
*
* @param self the string to split
* @return String[] result of split
* @since 1.5.0
*/
public static String[] split(String self) {
StringTokenizer st = new StringTokenizer(self);
String[] strings = new String[st.countTokens()];
for (int i = 0; i < strings.length; i++) {
strings[i] = st.nextToken();
}
return strings;
}
/**
* Iterates through the given CharSequence line by line, splitting each line using
* the given separator. The list of tokens for each line is then passed to
* the given closure.
*
* @param self a CharSequence
* @param regex the delimiting regular expression
* @param closure a closure
* @return the last value returned by the closure
* @throws java.io.IOException if an error occurs
* @throws java.util.regex.PatternSyntaxException if the regular expression's syntax is invalid
* @see #splitEachLine(String, String, groovy.lang.Closure)
* @since 1.8.2
*/
public static T splitEachLine(CharSequence self, CharSequence regex, Closure closure) throws IOException {
return splitEachLine(self.toString(), regex.toString(), closure);
}
/**
* Iterates through the given CharSequence line by line, splitting each line using
* the given separator Pattern. The list of tokens for each line is then passed to
* the given closure.
*
* @param self a CharSequence
* @param pattern the regular expression Pattern for the delimiter
* @param closure a closure
* @return the last value returned by the closure
* @throws java.io.IOException if an error occurs
* @see #splitEachLine(String, java.util.regex.Pattern, groovy.lang.Closure)
* @since 1.8.2
*/
public static T splitEachLine(CharSequence self, Pattern pattern, Closure closure) throws IOException {
return splitEachLine(self.toString(), pattern, closure);
}
/**
* Iterates through the given String line by line, splitting each line using
* the given separator Pattern. The list of tokens for each line is then passed to
* the given closure.
*
* @param self a String
* @param pattern the regular expression Pattern for the delimiter
* @param closure a closure
* @return the last value returned by the closure
* @throws java.io.IOException if an error occurs
* @see java.util.regex.Pattern#split(CharSequence)
* @since 1.6.8
*/
public static T splitEachLine(String self, Pattern pattern, Closure closure) throws IOException {
final List list = readLines(self);
T result = null;
for (String line : list) {
List vals = Arrays.asList(pattern.split(line));
result = closure.call(vals);
}
return result;
}
/**
* Iterates through the given String line by line, splitting each line using
* the given separator. The list of tokens for each line is then passed to
* the given closure.
*
* @param self a String
* @param regex the delimiting regular expression
* @param closure a closure
* @return the last value returned by the closure
* @throws java.io.IOException if an error occurs
* @throws java.util.regex.PatternSyntaxException if the regular expression's syntax is invalid
* @see String#split(String)
* @since 1.5.5
*/
public static T splitEachLine(String self, String regex, Closure closure) throws IOException {
return splitEachLine(self, Pattern.compile(regex), closure);
}
/**
* Strip leading spaces from every line in a CharSequence. The
* line with the least number of leading spaces determines
* the number to remove. Lines only containing whitespace are
* ignored when calculating the number of leading spaces to strip.
*
* @param self The CharSequence to strip the leading spaces from
* @return the stripped CharSequence
* @see #stripIndent(String)
* @since 1.8.2
*/
public static CharSequence stripIndent(CharSequence self) {
return stripIndent(self.toString());
}
/**
* Strip numChar leading characters from
* every line in a CharSequence.
*
* @param self The CharSequence to strip the characters from
* @param numChars The number of characters to strip
* @return the stripped CharSequence
* @since 1.8.2
*/
public static CharSequence stripIndent(CharSequence self, int numChars) {
return stripIndent(self);
}
/**
* Strip leading spaces from every line in a String. The
* line with the least number of leading spaces determines
* the number to remove. Lines only containing whitespace are
* ignored when calculating the number of leading spaces to strip.
*
* assert ' A\n B\nC' == ' A\n B\n C'.stripIndent()
*
*
* @param self The String to strip the leading spaces from
* @return the stripped String
* @see #stripIndent(String, int)
* @since 1.7.3
*/
public static String stripIndent(String self) {
if (self.length() == 0) return self;
int runningCount = -1;
try {
for (String line : readLines(self)) {
// don't take blank lines into account for calculating the indent
if (isAllWhitespace(line)) continue;
if (runningCount == -1) runningCount = line.length();
runningCount = findMinimumLeadingSpaces(line, runningCount);
if (runningCount == 0) break;
}
} catch (IOException e) {
/* ignore */
}
return stripIndent(self, runningCount == -1 ? 0 : runningCount);
}
/**
* Strip numChar leading characters from
* every line in a String.
*
* assert 'DEF\n456' == '''ABCDEF\n123456'''.stripIndent(3)
*
*
* @param self The String to strip the characters from
* @param numChars The number of characters to strip
* @return the stripped String
* @since 1.7.3
*/
public static String stripIndent(String self, int numChars) {
if (self.length() == 0 || numChars <= 0) return self;
try {
StringBuilder builder = new StringBuilder();
for (String line : readLines(self)) {
// normalize an empty or whitespace line to \n
// or strip the indent for lines containing non-space characters
if (!isAllWhitespace(line)) {
builder.append(stripIndentFromLine(line, numChars));
}
builder.append("\n");
}
// remove the normalized ending line ending if it was not present
if (!self.endsWith("\n")) {
builder.deleteCharAt(builder.length() - 1);
}
return builder.toString();
} catch (IOException e) {
/* ignore */
}
return self;
}
// TODO expose this for stream based stripping?
private static String stripIndentFromLine(String line, int numChars) {
int length = line.length();
return numChars <= length ? line.substring(numChars) : "";
}
/**
* Strip leading whitespace/control characters followed by '|' from
* every line in a CharSequence.
*
* @param self The CharSequence to strip the margin from
* @return the stripped CharSequence
* @see #stripMargin(CharSequence, char)
* @since 1.8.2
*/
public static CharSequence stripMargin(CharSequence self) {
return stripMargin(self, '|');
}
/**
* Strip leading whitespace/control characters followed by marginChar from
* every line in a String.
*
* @param self The CharSequence to strip the margin from
* @param marginChar Any character that serves as margin delimiter
* @return the stripped CharSequence
* @see #stripMargin(String, char)
* @since 1.8.2
*/
public static CharSequence stripMargin(CharSequence self, char marginChar) {
return stripMargin(self.toString(), marginChar);
}
/**
* Strip leading whitespace/control characters followed by marginChar from
* every line in a CharSequence.
*
* @param self The CharSequence to strip the margin from
* @param marginChar Any character that serves as margin delimiter
* @return the stripped CharSequence
* @see #stripMargin(String, String)
* @since 1.8.2
*/
public static String stripMargin(CharSequence self, CharSequence marginChar) {
return stripMargin(self.toString(), marginChar.toString());
}
/**
* Strip leading whitespace/control characters followed by '|' from
* every line in a String.
*
* assert 'ABC\n123\n456' == '''ABC
* |123
* |456'''.stripMargin()
*
*
* @param self The String to strip the margin from
* @return the stripped String
* @see #stripMargin(String, char)
* @since 1.7.3
*/
public static String stripMargin(String self) {
return stripMargin(self, '|');
}
/**
* Strip leading whitespace/control characters followed by marginChar from
* every line in a String.
*
* assert 'ABC\n123\n456' == '''ABC
* *123
* *456'''.stripMargin('*')
*
*
* @param self The String to strip the margin from
* @param marginChar Any character that serves as margin delimiter
* @return the stripped String
* @since 1.7.3
*/
public static String stripMargin(String self, char marginChar) {
if (self.length() == 0) return self;
try {
StringBuilder builder = new StringBuilder();
for (String line : readLines(self)) {
builder.append(stripMarginFromLine(line, marginChar));
builder.append("\n");
}
// remove the normalized ending line ending if it was not present
if (!self.endsWith("\n")) {
builder.deleteCharAt(builder.length() - 1);
}
return builder.toString();
} catch (IOException e) {
/* ignore */
}
return self;
}
/**
* Strip leading whitespace/control characters followed by marginChar from
* every line in a String.
*
* @param self The String to strip the margin from
* @param marginChar Any character that serves as margin delimiter
* @return the stripped String
* @see #stripMargin(String, char)
* @since 1.7.3
*/
public static String stripMargin(String self, String marginChar) {
if (marginChar == null || marginChar.length() == 0) return stripMargin(self, '|');
// TODO IllegalArgumentException for marginChar.length() > 1 ? Or support String as marker?
return stripMargin(self, marginChar.charAt(0));
}
// TODO expose this for stream based stripping?
private static String stripMarginFromLine(String line, char marginChar) {
int length = line.length();
int index = 0;
while (index < length && line.charAt(index) <= ' ') index++;
return (index < length && line.charAt(index) == marginChar) ? line.substring(index + 1) : line;
}
/**
* Returns the first num
elements from this CharSequence.
*
* def text = "Groovy"
* assert text.take( 0 ) == ''
* assert text.take( 2 ) == 'Gr'
* assert text.take( 7 ) == 'Groovy'
*
*
* @param self the original CharSequence
* @param num the number of chars to take from this CharSequence
* @return a CharSequence consisting of the first num
chars,
* or else the whole CharSequence if it has less then num
elements.
* @since 1.8.1
*/
public static CharSequence take(CharSequence self, int num) {
if (num < 0) {
return self.subSequence(0, 0);
}
if (self.length() <= num) {
return self;
}
return self.subSequence(0, num);
}
/**
* Returns the longest prefix of this CharSequence where each
* element passed to the given closure evaluates to true.
*
*
* def text = "Groovy"
* assert text.takeWhile{ it < 'A' } == ''
* assert text.takeWhile{ it < 'Z' } == 'G'
* assert text.takeWhile{ it != 'v' } == 'Groo'
* assert text.takeWhile{ it < 'z' } == 'Groovy'
*
*
* @param self the original CharSequence
* @param condition the closure that must evaluate to true to continue taking elements
* @return a prefix of elements in the CharSequence where each
* element passed to the given closure evaluates to true
* @since 2.0.0
*/
public static CharSequence takeWhile(CharSequence self, Closure condition) {
int num = 0;
BooleanClosureWrapper bcw = new BooleanClosureWrapper(condition);
while (num < self.length()) {
char value = self.charAt(num);
if (bcw.call(value)) {
num += 1;
} else {
break;
}
}
return take(self, num);
}
/**
* Parse a CharSequence into a BigDecimal
*
* @param self a CharSequence
* @return a BigDecimal
* @see #toBigDecimal(String)
* @since 1.8.2
*/
public static BigDecimal toBigDecimal(CharSequence self) {
return toBigDecimal(self.toString());
}
/**
* Parse a String into a BigDecimal
*
* @param self a String
* @return a BigDecimal
* @since 1.0
*/
public static BigDecimal toBigDecimal(String self) {
return new BigDecimal(self.trim());
}
/**
* Parse a CharSequence into a BigInteger
*
* @param self a CharSequence
* @return a BigInteger
* @see #toBigInteger(String)
* @since 1.8.2
*/
public static BigInteger toBigInteger(CharSequence self) {
return toBigInteger(self.toString());
}
/**
* Parse a String into a BigInteger
*
* @param self a String
* @return a BigInteger
* @since 1.0
*/
public static BigInteger toBigInteger(String self) {
return new BigInteger(self.trim());
}
/**
* Converts the given string into a Boolean object.
* If the trimmed string is "true", "y" or "1" (ignoring case)
* then the result is true otherwise it is false.
*
* @param self a String
* @return The Boolean value
* @since 1.0
*/
public static Boolean toBoolean(String self) {
final String trimmed = self.trim();
if ("true".equalsIgnoreCase(trimmed) || "y".equalsIgnoreCase(trimmed) || "1".equals(trimmed)) {
return Boolean.TRUE;
} else {
return Boolean.FALSE;
}
}
/**
* Converts the given string into a Character object
* using the first character in the string.
*
* @param self a String
* @return the first Character
* @since 1.0
*/
public static Character toCharacter(String self) {
return self.charAt(0);
}
/**
* Parse a CharSequence into a Double
*
* @param self a CharSequence
* @return a Double
* @see #toDouble(String)
* @since 1.8.2
*/
public static Double toDouble(CharSequence self) {
return toDouble(self.toString());
}
/**
* Parse a String into a Double
*
* @param self a String
* @return a Double
* @since 1.0
*/
public static Double toDouble(String self) {
return Double.valueOf(self.trim());
}
/**
* Parse a CharSequence into a Float
*
* @param self a CharSequence
* @return a Float
* @see #toFloat(String)
* @since 1.8.2
*/
public static Float toFloat(CharSequence self) {
return toFloat(self.toString());
}
/**
* Parse a String into a Float
*
* @param self a String
* @return a Float
* @since 1.0
*/
public static Float toFloat(String self) {
return Float.valueOf(self.trim());
}
/**
* Parse a CharSequence into an Integer
*
* @param self a CharSequence
* @return an Integer
* @see #toInteger(String)
* @since 1.8.2
*/
public static Integer toInteger(CharSequence self) {
return toInteger(self.toString());
}
/**
* Parse a String into an Integer
*
* @param self a String
* @return an Integer
* @since 1.0
*/
public static Integer toInteger(String self) {
return Integer.valueOf(self.trim());
}
/**
* Tokenize a CharSequence (with a whitespace as the delimiter).
*
* @param self a CharSequence
* @return a List of tokens
* @see #tokenize(String)
* @since 1.8.2
*/
public static List tokenize(CharSequence self) {
return new ArrayList(tokenize(self.toString()));
}
/**
* Tokenize a CharSequence based on the given character delimiter.
*
* @param self a CharSequence
* @param token the delimiter
* @return a List of tokens
* @see #tokenize(String, Character)
* @since 1.8.2
*/
public static List tokenize(CharSequence self, Character token) {
return tokenize(self, token.toString());
}
/**
* Tokenize a CharSequence based on the given CharSequence delimiter.
*
* @param self a CharSequence
* @param token the delimiter
* @return a List of tokens
* @see #tokenize(String, String)
* @since 1.8.2
*/
public static List tokenize(CharSequence self, CharSequence token) {
return new ArrayList(tokenize(self.toString(), token.toString()));
}
/**
* Tokenize a String (with a whitespace as the delimiter).
*
* @param self a String
* @return a List of tokens
* @see java.util.StringTokenizer#StringTokenizer(String)
* @since 1.0
*/
@SuppressWarnings("unchecked")
public static List tokenize(String self) {
return InvokerHelper.asList(new StringTokenizer(self));
}
/**
* Tokenize a String based on the given character delimiter.
* For example:
*
* char pathSep = ':'
* assert "/tmp:/usr".tokenize(pathSep) == ["/tmp", "/usr"]
*
*
* @param self a String
* @param token the delimiter
* @return a List of tokens
* @see java.util.StringTokenizer#StringTokenizer(String, String)
* @since 1.7.2
*/
public static List tokenize(String self, Character token) {
return tokenize(self, token.toString());
}
/**
* Tokenize a String based on the given string delimiter.
*
* @param self a String
* @param token the delimiter
* @return a List of tokens
* @see java.util.StringTokenizer#StringTokenizer(String, String)
* @since 1.0
*/
@SuppressWarnings("unchecked")
public static List tokenize(String self, String token) {
return InvokerHelper.asList(new StringTokenizer(self, token));
}
/**
* Converts the given CharSequence into a List of CharSequence of one character.
*
* @param self a CharSequence
* @return a List of characters (a 1-character CharSequence)
* @see #toSet(String)
* @since 1.8.2
*/
public static List toList(CharSequence self) {
return new ArrayList(toList(self.toString()));
}
/**
* Converts the given String into a List of strings of one character.
*
* @param self a String
* @return a List of characters (a 1-character String)
* @since 1.0
*/
public static List toList(String self) {
int size = self.length();
List answer = new ArrayList(size);
for (int i = 0; i < size; i++) {
answer.add(self.substring(i, i + 1));
}
return answer;
}
/**
* Parse a CharSequence into a Long
*
* @param self a CharSequence
* @return a Long
* @see #toLong(String)
* @since 1.8.2
*/
public static Long toLong(CharSequence self) {
return toLong(self.toString());
}
/**
* Parse a String into a Long
*
* @param self a String
* @return a Long
* @since 1.0
*/
public static Long toLong(String self) {
return Long.valueOf(self.trim());
}
/**
* Converts the given CharSequence into a Set of unique CharSequence of one character.
*
* @param self a CharSequence
* @return a Set of unique character CharSequence (each a 1-character CharSequence)
* @see #toSet(String)
* @since 1.8.2
*/
public static Set toSet(CharSequence self) {
return new HashSet(toList(self));
}
/**
* Converts the given String into a Set of unique strings of one character.
*
* Example usage:
*
* assert 'groovy'.toSet() == ['v', 'g', 'r', 'o', 'y'] as Set
* assert "abc".toSet().iterator()[0] instanceof String
*
*
* @param self a String
* @return a Set of unique character Strings (each a 1-character String)
* @since 1.8.0
*/
public static Set toSet(String self) {
return new HashSet(toList(self));
}
/**
* Parse a CharSequence into a Short
*
* @param self a CharSequence
* @return a Short
* @see #toShort(String)
* @since 1.8.2
*/
public static Short toShort(CharSequence self) {
return toShort(self.toString());
}
/**
* Parse a String into a Short
*
* @param self a String
* @return a Short
* @since 1.5.7
*/
public static Short toShort(String self) {
return Short.valueOf(self.trim());
}
/**
* Translates a string by replacing characters from the sourceSet with characters from replacementSet.
*
* @param self the CharSequence that is to be translated
* @param sourceSet the set of characters to translate from
* @param replacementSet the set of replacement characters
* @return The resulting translated CharSequence
* @see #tr(String, String, String)
* @since 1.8.2
*/
public static CharSequence tr(final CharSequence self, CharSequence sourceSet, CharSequence replacementSet) throws ClassNotFoundException {
return tr(self.toString(), sourceSet.toString(), replacementSet.toString());
}
/**
* Translates a string by replacing characters from the sourceSet with characters from replacementSet.
* If the first character from sourceSet appears in the string, it will be replaced with the first character from replacementSet.
* If the second character from sourceSet appears in the string, it will be replaced with the second character from replacementSet.
* and so on for all provided replacement characters.
*
* Here is an example which converts the vowels in a word from lower to uppercase:
*
* assert 'hello'.tr('aeiou', 'AEIOU') == 'hEllO'
*
* A character range using regex-style syntax can also be used, e.g. here is an example which converts a word from lower to uppercase:
*
* assert 'hello'.tr('a-z', 'A-Z') == 'HELLO'
*
* Hyphens at the start or end of sourceSet or replacementSet are treated as normal hyphens and are not
* considered to be part of a range specification. Similarly, a hyphen immediately after an earlier range
* is treated as a normal hyphen. So, '-x', 'x-' have no ranges while 'a-c-e' has the range 'a-c' plus
* the '-' character plus the 'e' character.
*
* Unlike the unix tr command, Groovy's tr command supports reverse ranges, e.g.:
*
* assert 'hello'.tr('z-a', 'Z-A') == 'HELLO'
*
* If replacementSet is smaller than sourceSet, then the last character from replacementSet is used as the replacement for all remaining source characters as shown here:
*
* assert 'Hello World!'.tr('a-z', 'A') == 'HAAAA WAAAA!'
*
* If sourceSet contains repeated characters, the last specified replacement is used as shown here:
*
* assert 'Hello World!'.tr('lloo', '1234') == 'He224 W4r2d!'
*
* The functionality provided by tr can be achieved using regular expressions but tr provides a much more compact
* notation and efficient implementation for certain scenarios.
*
* @param self the string that is to be translated
* @param sourceSet the set of characters to translate from
* @param replacementSet the set of replacement characters
* @return The resulting translated String
* @see org.codehaus.groovy.util.StringUtil#tr(String, String, String)
* @since 1.7.3
*/
public static String tr(final String self, String sourceSet, String replacementSet) throws ClassNotFoundException {
return (String) InvokerHelper.invokeStaticMethod("org.codehaus.groovy.util.StringUtil", "tr", new Object[]{self, sourceSet, replacementSet});
}
/**
* Replaces sequences of whitespaces with tabs using tabStops of size 8.
*
* @param self A CharSequence to unexpand
* @return The unexpanded CharSequence
* @see #unexpand(String)
* @since 1.8.2
*/
public static CharSequence unexpand(CharSequence self) {
return unexpand(self.toString());
}
/**
* Replaces sequences of whitespaces with tabs.
*
* @param self A CharSequence to unexpand
* @param tabStop The number of spaces a tab represents
* @return The unexpanded CharSequence
* @see #unexpand(String, int)
* @since 1.8.2
*/
public static CharSequence unexpand(CharSequence self, int tabStop) {
return unexpand(self.toString(), tabStop);
}
/**
* Replaces sequences of whitespaces with tabs using tabStops of size 8.
*
* @param self A String to unexpand
* @return The unexpanded String
* @since 1.7.3
* @see #unexpand(String, int)
*/
public static String unexpand(String self) {
return unexpand(self, 8);
}
/**
* Replaces sequences of whitespaces with tabs.
*
* @param self A String to unexpand
* @param tabStop The number of spaces a tab represents
* @return The unexpanded String
* @since 1.7.3
*/
public static String unexpand(String self, int tabStop) {
if (self.length() == 0) return self;
try {
StringBuilder builder = new StringBuilder();
for (String line : readLines(self)) {
builder.append(unexpandLine(line, tabStop));
builder.append("\n");
}
// remove the normalized ending line ending if it was not present
if (!self.endsWith("\n")) {
builder.deleteCharAt(builder.length() - 1);
}
return builder.toString();
} catch (IOException e) {
/* ignore */
}
return self;
}
/**
* Replaces sequences of whitespaces with tabs within a line.
*
* @param self A line to unexpand
* @param tabStop The number of spaces a tab represents
* @return The unexpanded CharSequence
* @see #unexpandLine(String, int)
* @since 1.8.2
*/
public static CharSequence unexpandLine(CharSequence self, int tabStop) {
return unexpandLine(self.toString(), tabStop);
}
/**
* Replaces sequences of whitespaces with tabs within a line.
*
* @param self A line to unexpand
* @param tabStop The number of spaces a tab represents
* @return The unexpanded String
* @since 1.7.3
*/
public static String unexpandLine(String self, int tabStop) {
StringBuilder builder = new StringBuilder(self);
int index = 0;
while (index + tabStop < builder.length()) {
// cut original string in tabstop-length pieces
String piece = builder.substring(index, index + tabStop);
// count trailing whitespace characters
int count = 0;
while ((count < tabStop) && (Character.isWhitespace(piece.charAt(tabStop - (count + 1)))))
count++;
// replace if whitespace was found
if (count > 0) {
piece = piece.substring(0, tabStop - count) + '\t';
builder.replace(index, index + tabStop, piece);
index = index + tabStop - (count - 1);
} else
index = index + tabStop;
}
return builder.toString();
}
}