diff -r 61fff1345ee6 -r cc0f117f4405 src/java.base/share/classes/java/lang/String.java --- a/src/java.base/share/classes/java/lang/String.java Thu Jun 06 08:11:25 2019 -0700 +++ b/src/java.base/share/classes/java/lang/String.java Thu Jun 06 12:24:44 2019 -0300 @@ -36,6 +36,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.Formatter; +import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Optional; @@ -2797,11 +2798,6 @@ return indexOfNonWhitespace() == length(); } - private Stream lines(int maxLeading, int maxTrailing) { - return isLatin1() ? StringLatin1.lines(value, maxLeading, maxTrailing) - : StringUTF16.lines(value, maxLeading, maxTrailing); - } - /** * Returns a stream of lines extracted from this string, * separated by line terminators. @@ -2833,7 +2829,7 @@ * @since 11 */ public Stream lines() { - return lines(0, 0); + return isLatin1() ? StringLatin1.lines(value) : StringUTF16.lines(value); } /** @@ -2873,12 +2869,10 @@ * @since 12 */ public String indent(int n) { - return isEmpty() ? "" : indent(n, false); - } - - private String indent(int n, boolean removeBlanks) { - Stream stream = removeBlanks ? lines(Integer.MAX_VALUE, Integer.MAX_VALUE) - : lines(); + if (isEmpty()) { + return ""; + } + Stream stream = lines(); if (n > 0) { final String spaces = " ".repeat(n); stream = stream.map(s -> spaces + s); @@ -2901,6 +2895,123 @@ } /** + * Returns a string whose value is this string, with incidental + * {@linkplain Character#isWhitespace(int) white space} removed from + * the beginning and end of every line. + *

+ * Incidental {@linkplain Character#isWhitespace(int) white space} + * is often present in a text block to align the content with the opening + * delimiter. For example, in the following code, dots represent incidental + * {@linkplain Character#isWhitespace(int) white space}: + *

+     * String html = """
+     * ..............<html>
+     * ..............    <body>
+     * ..............        <p>Hello, world</p>
+     * ..............    </body>
+     * ..............</html>
+     * ..............""";
+     * 
+ * This method treats the incidental + * {@linkplain Character#isWhitespace(int) white space} as indentation to be + * stripped, producing a string that preserves the relative indentation of + * the content. Using | to visualize the start of each line of the string: + *
+     * |<html>
+     * |    <body>
+     * |        <p>Hello, world</p>
+     * |    </body>
+     * |</html>
+     * 
+ * First, the individual lines of this string are extracted as if by using + * {@link String#lines()}. + *

+ * Then, the minimum indentation (min) is determined as follows. + * For each non-blank line (as defined by {@link String#isBlank()}), the + * leading {@linkplain Character#isWhitespace(int) white space} characters are + * counted. The leading {@linkplain Character#isWhitespace(int) white space} + * characters on the last line are also counted even if + * {@linkplain String#isBlank() blank}. The min value is the smallest + * of these counts. + *

+ * For each {@linkplain String#isBlank() non-blank} line, min leading + * {@linkplain Character#isWhitespace(int) white space} characters are removed, + * and any trailing {@linkplain Character#isWhitespace(int) white space} + * characters are removed. {@linkplain String#isBlank() Blank} lines are + * replaced with the empty string. + * + *

+ * Finally, the lines are joined into a new string, using the LF character + * {@code "\n"} (U+000A) to separate lines. + * + * @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 + * {@linkplain Character#isWhitespace(int) white space}. + * The line count of the result will be the same as line count of this + * string. + * If this string ends with a line terminator then the result will end + * with a line terminator. + * + * @implNote + * This method treats all {@linkplain Character#isWhitespace(int) white space} + * characters as having equal width. As long as the indentation on every + * line is consistently composed of the same character sequences, then the + * result will be as described above. + * + * @return string with incidental indentation removed and line + * terminators normalized + * + * @see String#lines() + * @see String#isBlank() + * @see String#indent(int) + * @see Character#isWhitespace(int) + * + * @since 13 + * + * @deprecated This method is associated with text blocks, a preview language feature. + * Text blocks and/or this method may be changed or removed in a future release. + */ + @Deprecated(forRemoval=true, since="13") + public String stripIndent() { + int length = length(); + if (length == 0) { + return ""; + } + char lastChar = charAt(length - 1); + boolean optOut = lastChar == '\n' || lastChar == '\r'; + List lines = lines().collect(Collectors.toList()); + final int outdent = optOut ? 0 : outdent(lines); + return lines.stream() + .map(line -> { + int firstNonWhitespace = line.indexOfNonWhitespace(); + int lastNonWhitespace = line.lastIndexOfNonWhitespace(); + int incidentalWhitespace = Math.min(outdent, firstNonWhitespace); + return firstNonWhitespace > lastNonWhitespace + ? "" : line.substring(incidentalWhitespace, lastNonWhitespace); + }) + .collect(Collectors.joining("\n", "", optOut ? "\n" : "")); + } + + private static int outdent(List lines) { + // Note: outdent is guaranteed to be zero or positive number. + // If there isn't a non-blank line then the last must be blank + int outdent = Integer.MAX_VALUE; + for (String line : lines) { + int leadingWhitespace = line.indexOfNonWhitespace(); + if (leadingWhitespace != line.length()) { + outdent = Integer.min(outdent, leadingWhitespace); + } + } + String lastLine = lines.get(lines.size() - 1); + if (lastLine.isBlank()) { + outdent = Integer.min(outdent, lastLine.length()); + } + return outdent; + } + + /** * This method allows the application of a function to {@code this} * string. The function should expect a single String argument * and produce an {@code R} result.