Checkout StringUtils parts from 7.0.x branch

This commit is contained in:
Clément Fournier
2020-09-01 17:53:33 +02:00
parent 21aeabdf45
commit 251a217a59
2 changed files with 145 additions and 0 deletions

View File

@ -38,6 +38,114 @@ public final class StringUtil {
private StringUtil() {
}
/**
* Returns the (1-based) line number of the character at the given index.
* Line terminators (\r, \n) are assumed to be on the line they *end*
* and not on the following line. The method also accepts that the given
* offset be the length of the string (in which case there's no targeted character),
* to get the line number of a character that would be inserted at
* the end of the string.
*
* <pre>
*
* lineNumberAt("a\nb", 0) = 1
* lineNumberAt("a\nb", 1) = 1
* lineNumberAt("a\nb", 2) = 2
* lineNumberAt("a\nb", 3) = 2 // charAt(3) doesn't exist though
* lineNumberAt("a\nb", 4) = -1
*
* lineNumberAt("", 0) = 1
* lineNumberAt("", _) = -1
*
* </pre>
*
* @param charSeq Char sequence
* @param offsetInclusive Offset in the sequence of the targeted character.
* May be the length of the sequence.
* @return -1 if the offset is not in {@code [0, length]}, otherwise
* the line number
*/
public static int lineNumberAt(CharSequence charSeq, int offsetInclusive) {
int len = charSeq.length();
if (offsetInclusive > len || offsetInclusive < 0) {
return -1;
}
int l = 1;
for (int curOffset = 0; curOffset < offsetInclusive; curOffset++) {
// if we end up outside the string, then the line is undefined
if (curOffset >= len) {
return -1;
}
char c = charSeq.charAt(curOffset);
if (c == '\n') {
l++;
} else if (c == '\r') {
if (curOffset + 1 < len && charSeq.charAt(curOffset + 1) == '\n') {
if (curOffset == offsetInclusive - 1) {
// the CR is assumed to be on the same line as the LF
return l;
}
curOffset++; // SUPPRESS CHECKSTYLE jump to after the \n
}
l++;
}
}
return l;
}
/**
* Returns the (1-based) column number of the character at the given index.
* Line terminators are by convention taken to be part of the line they end,
* and not the new line they start. Each character has width 1 (including {@code \t}).
* The method also accepts that the given offset be the length of the
* string (in which case there's no targeted character), to get the column
* number of a character that would be inserted at the end of the string.
*
* <pre>
*
* columnNumberAt("a\nb", 0) = 1
* columnNumberAt("a\nb", 1) = 2
* columnNumberAt("a\nb", 2) = 1
* columnNumberAt("a\nb", 3) = 2 // charAt(3) doesn't exist though
* columnNumberAt("a\nb", 4) = -1
*
* columnNumberAt("a\r\n", 2) = 3
*
* </pre>
*
* @param charSeq Char sequence
* @param offsetInclusive Offset in the sequence
* @return -1 if the offset is not in {@code [0, length]}, otherwise
* the column number
*/
public static int columnNumberAt(CharSequence charSeq, final int offsetInclusive) {
if (offsetInclusive == charSeq.length()) {
return charSeq.length() == 0 ? 1 : 1 + columnNumberAt(charSeq, offsetInclusive - 1);
} else if (offsetInclusive > charSeq.length() || offsetInclusive < 0) {
return -1;
}
int col = 0;
char next = 0;
for (int i = offsetInclusive; i >= 0; i--) {
char c = charSeq.charAt(i);
if (offsetInclusive != i) {
if (c == '\n' || c == '\r' && next != '\n') {
return col;
}
}
col++;
next = c;
}
return col;
}
/**
* Formats a double to a percentage, keeping {@code numDecimal} decimal places.
*

View File

@ -10,6 +10,43 @@ import org.junit.Test;
public class StringUtilTest {
@Test
public void testColumnNumber() {
assertEquals(-1, StringUtil.columnNumberAt("f\rah\nb", -1));
assertEquals(1, StringUtil.columnNumberAt("f\rah\nb", 0));
assertEquals(2, StringUtil.columnNumberAt("f\rah\nb", 1));
assertEquals(1, StringUtil.columnNumberAt("f\rah\nb", 2));
assertEquals(2, StringUtil.columnNumberAt("f\rah\nb", 3));
assertEquals(3, StringUtil.columnNumberAt("f\rah\nb", 4));
assertEquals(1, StringUtil.columnNumberAt("f\rah\nb", 5));
assertEquals(2, StringUtil.columnNumberAt("f\rah\nb", 6));
assertEquals(-1, StringUtil.columnNumberAt("f\rah\nb", 7));
}
@Test
public void testColumnNumberCrLf() {
assertEquals(-1, StringUtil.columnNumberAt("f\r\nb", -1));
assertEquals(1, StringUtil.columnNumberAt("f\r\nb", 0));
assertEquals(2, StringUtil.columnNumberAt("f\r\nb", 1));
assertEquals(3, StringUtil.columnNumberAt("f\r\nb", 2));
assertEquals(1, StringUtil.columnNumberAt("f\r\nb", 3));
assertEquals(2, StringUtil.columnNumberAt("f\r\nb", 4));
assertEquals(-1, StringUtil.columnNumberAt("f\r\nb", 5));
}
@Test
public void testColumnNumberTrailing() {
assertEquals(1, StringUtil.columnNumberAt("\n", 0));
assertEquals(2, StringUtil.columnNumberAt("\n", 1));
assertEquals(-1, StringUtil.columnNumberAt("\n", 2));
}
@Test
public void testColumnNumberEmpty() {
assertEquals(1, StringUtil.columnNumberAt("", 0));
assertEquals(-1, StringUtil.columnNumberAt("", 1));
}
@Test
public void testReplaceWithOneChar() {
assertEquals("faa", StringUtil.replaceString("foo", 'o', "a"));