All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.groovy.runtime.StringGroovyMethods Maven / Gradle / Ivy

The newest version!
/*
 * 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 groovy.transform.stc.ClosureParams;
import groovy.transform.stc.FromString;
import groovy.transform.stc.SimpleType;
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 (c.isEnum()) { 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, @ClosureParams(value=SimpleType.class, options="char") 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, @ClosureParams(value=FromString.class, options={"String","String,Integer"}) 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, @ClosureParams(value=FromString.class, options={"String","String,Integer"}) 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, @ClosureParams(value=FromString.class, options={"String","String,Integer"}) 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, @ClosureParams(value=FromString.class, options={"String","String,Integer"}) 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, @ClosureParams(value=SimpleType.class, options="char") 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, @ClosureParams(value=FromString.class, options={"List","String[]"}) 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, @ClosureParams(value=FromString.class, options={"List","String[]"}) 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, @ClosureParams(value=FromString.class, options={"List","String[]"}) 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, @ClosureParams(value=FromString.class, options={"List","String[]"}) 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 (if any) * @return a CharSequence containing the result of calling the closure (calling toString() if needed), 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, @ClosureParams(value=SimpleType.class, options="java.lang.String[]") 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 (if any) * @return a CharSequence containing the result of calling the closure (calling toString() if needed), 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, @ClosureParams(value=SimpleType.class, options="java.lang.String[]") 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 (if any) * @return a String containing the result of calling the closure (calling toString() if needed), or null if the regex pattern doesn't match * @since 1.6.1 */ public static String find(String self, Pattern pattern, @ClosureParams(value=SimpleType.class, options="java.lang.String[]") 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 (if any) * @return a String containing the result of the closure (calling toString() if needed), or null if the regex pattern doesn't match * @since 1.6.1 */ public static String find(String self, String regex, @ClosureParams(value=SimpleType.class, options="java.lang.String[]") 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 (if any) * @return a List containing all results from calling the closure with each full match (and potentially capturing groups) 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, @ClosureParams(value=SimpleType.class, options="java.lang.String[]") 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 (if any) * @return a List containing all results from calling the closure with each full match (and potentially capturing groups) 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, @ClosureParams(value=SimpleType.class, options="java.lang.String[]") 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 (if any) * @return a List containing all results from calling the closure with each full match (and potentially capturing groups) 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, @ClosureParams(value=SimpleType.class, options="java.lang.String[]") 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 (if any) * @return a List containing all results from calling the closure with each full match (and potentially capturing groups) 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, @ClosureParams(value=SimpleType.class, options="java.lang.String[]") 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) { RangeInfo info = subListBorders(text.length(), range); CharSequence sequence = text.subSequence(info.from, info.to); return info.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) { RangeInfo info = subListBorders(text.length(), range); String answer = text.substring(info.from, info.to); if (info.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 the regex pattern within self with '' and returns the result. * * @param self a String * @param pattern a Pattern representing the part to remove * @return a String minus the part to be removed * @since 2.2.0 */ public static String minus(String self, Pattern pattern) { return pattern.matcher(self).replaceFirst(""); } /** * Remove a part of a String. This replaces the first occurrence * of target.toString() within self with '' and returns the result. * * @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) { 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 the String representation of the given operand to this string. * * @param left a String * @param value any CharSequence * @return the new string with the object appended * @since 2.2 */ public static String plus(String left, CharSequence value) { return left+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, @ClosureParams(value=FromString.class, options={"List","String[]"}) 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, @ClosureParams(value=SimpleType.class, options="java.lang.String[]") 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, @ClosureParams(value=FromString.class, options={"List","String[]"}) 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 @ClosureParams(value=FromString.class, options={"List","String[]"}) 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, @ClosureParams(value=SimpleType.class, options="java.lang.String[]") 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 @ClosureParams(value=FromString.class, options={"List","String[]"}) 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 @ClosureParams(value=FromString.class, options={"List","String[]"}) 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 @ClosureParams(value=FromString.class, options={"List","String[]"}) 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 @ClosureParams(value=FromString.class, options={"List","String[]"}) 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 @ClosureParams(value=FromString.class, options={"List","String[]"}) 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, @ClosureParams(value=FromString.class,options="List") 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, @ClosureParams(value=FromString.class,options="List") 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, @ClosureParams(value=FromString.class,options="List") 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, @ClosureParams(value=FromString.class,options="List") 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, @ClosureParams(value=SimpleType.class, options="char") 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(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy