8200434: String::align, String::indent

Reviewed-by: abuckley, smarks, sherman, rriggs, jrose, sundar, igerasim, briangoetz, darcy, jjg
This commit is contained in:
Jim Laskey 2018-09-12 14:19:36 -03:00
parent 2065ebd890
commit 12dad310bb
4 changed files with 541 additions and 34 deletions

View file

@ -40,12 +40,15 @@ import java.util.StringJoiner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import jdk.internal.HotSpotIntrinsicCandidate;
import jdk.internal.vm.annotation.Stable;
import static java.util.function.Predicate.not;
/**
* The {@code String} class represents character strings. All
* string literals in Java programs, such as {@code "abc"}, are
@ -2755,12 +2758,9 @@ public final class String
return indexOfNonWhitespace() == length();
}
private int indexOfNonWhitespace() {
if (isLatin1()) {
return StringLatin1.indexOfNonWhitespace(value);
} else {
return StringUTF16.indexOfNonWhitespace(value);
}
private Stream<String> lines(int maxLeading, int maxTrailing) {
return isLatin1() ? StringLatin1.lines(value, maxLeading, maxTrailing)
: StringUTF16.lines(value, maxLeading, maxTrailing);
}
/**
@ -2794,8 +2794,181 @@ public final class String
* @since 11
*/
public Stream<String> lines() {
return isLatin1() ? StringLatin1.lines(value)
: StringUTF16.lines(value);
return lines(0, 0);
}
/**
* Adjusts the indentation of each line of this string based on the value of
* {@code n}, and normalizes line termination characters.
* <p>
* This string is conceptually separated into lines using
* {@link String#lines()}. Each line is then adjusted as described below
* and then suffixed with a line feed {@code "\n"} (U+000A). The resulting
* lines are then concatenated and returned.
* <p>
* If {@code n > 0} then {@code n} spaces (U+0020) are inserted at the
* beginning of each line. {@link String#isBlank() Blank lines} are
* unaffected.
* <p>
* If {@code n < 0} then up to {@code n}
* {@link Character#isWhitespace(int) white space characters} are removed
* from the beginning of each line. If a given line does not contain
* sufficient white space then all leading
* {@link Character#isWhitespace(int) white space characters} are removed.
* Each white space character is treated as a single character. In
* particular, the tab character {@code "\t"} (U+0009) is considered a
* single character; it is not expanded.
* <p>
* If {@code n == 0} then the line remains unchanged. However, line
* terminators are still normalized.
* <p>
*
* @param n number of leading
* {@link Character#isWhitespace(int) white space characters}
* to add or remove
*
* @return string with indentation adjusted and line endings normalized
*
* @see String#lines()
* @see String#isBlank()
* @see Character#isWhitespace(int)
*
* @since 12
*/
public String indent(int n) {
return isEmpty() ? "" : indent(n, false);
}
private String indent(int n, boolean removeBlanks) {
Stream<String> stream = removeBlanks ? lines(Integer.MAX_VALUE, Integer.MAX_VALUE)
: lines();
if (n > 0) {
final String spaces = " ".repeat(n);
stream = stream.map(s -> s.isBlank() ? s : spaces + s);
} else if (n == Integer.MIN_VALUE) {
stream = stream.map(s -> s.stripLeading());
} else if (n < 0) {
stream = stream.map(s -> s.substring(Math.min(-n, s.indexOfNonWhitespace())));
}
return stream.collect(Collectors.joining("\n", "", "\n"));
}
private int indexOfNonWhitespace() {
return isLatin1() ? StringLatin1.indexOfNonWhitespace(value)
: StringUTF16.indexOfNonWhitespace(value);
}
private int lastIndexOfNonWhitespace() {
return isLatin1() ? StringLatin1.lastIndexOfNonWhitespace(value)
: StringUTF16.lastIndexOfNonWhitespace(value);
}
/**
* Removes vertical and horizontal white space margins from around the
* essential body of a multi-line string, while preserving relative
* indentation.
* <p>
* This string is first conceptually separated into lines as if by
* {@link String#lines()}.
* <p>
* Then, the <i>minimum indentation</i> (min) is determined as follows. For
* each non-blank line (as defined by {@link String#isBlank()}), the
* leading {@link Character#isWhitespace(int) white space} characters are
* counted. The <i>min</i> value is the smallest of these counts.
* <p>
* For each non-blank line, <i>min</i> leading white space characters are
* removed. Each white space character is treated as a single character. In
* particular, the tab character {@code "\t"} (U+0009) is considered a
* single character; it is not expanded.
* <p>
* Leading and trailing blank lines, if any, are removed. Trailing spaces are
* preserved.
* <p>
* Each line is suffixed with a line feed character {@code "\n"} (U+000A).
* <p>
* Finally, the lines are concatenated into a single string and returned.
*
* @apiNote
* This method's primary purpose is to shift a block of lines as far as
* possible to the left, while preserving relative indentation. Lines
* that were indented the least will thus have no leading white space.
*
* Example:
* <blockquote><pre>
* `
* This is the first line
* This is the second line
* `.align();
*
* returns
* This is the first line
* This is the second line
* </pre></blockquote>
*
* @return string with margins removed and line terminators normalized
*
* @see String#lines()
* @see String#isBlank()
* @see String#indent(int)
* @see Character#isWhitespace(int)
*
* @since 12
*/
public String align() {
return align(0);
}
/**
* Removes vertical and horizontal white space margins from around the
* essential body of a multi-line string, while preserving relative
* indentation and with optional indentation adjustment.
* <p>
* Invoking this method is equivalent to:
* <blockquote>
* {@code this.align().indent(n)}
* </blockquote>
*
* @apiNote
* Examples:
* <blockquote><pre>
* `
* This is the first line
* This is the second line
* `.align(0);
*
* returns
* This is the first line
* This is the second line
*
*
* `
* This is the first line
* This is the second line
* `.align(4);
* returns
* This is the first line
* This is the second line
* </pre></blockquote>
*
* @param n number of leading white space characters
* to add or remove
*
* @return string with margins removed, indentation adjusted and
* line terminators normalized
*
* @see String#align()
*
* @since 12
*/
public String align(int n) {
if (isEmpty()) {
return "";
}
int outdent = lines().filter(not(String::isBlank))
.mapToInt(String::indexOfNonWhitespace)
.min()
.orElse(0);
return indent(n - outdent, true);
}
/**