8223967: Implement Text Blocks (Preview) in the Java compiler
Reviewed-by: vromero, jlahoda, abuckley
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java Thu Jun 06 12:24:44 2019 -0300
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java Thu Jun 06 12:24:44 2019 -0300
@@ -275,6 +275,11 @@
STATIC("static"),
/**
+ * Warn about issues relating to use of text blocks
+ */
+ TEXT_BLOCKS("text-blocks"),
+
+ /**
* Warn about issues relating to use of try blocks (i.e. try-with-resources)
*/
TRY("try"),
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java Thu Jun 06 12:24:44 2019 -0300
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java Thu Jun 06 12:24:44 2019 -0300
@@ -167,7 +167,8 @@
public boolean isPreview(Feature feature) {
if (feature == Feature.SWITCH_EXPRESSION ||
feature == Feature.SWITCH_MULTIPLE_CASE_LABELS ||
- feature == Feature.SWITCH_RULE)
+ feature == Feature.SWITCH_RULE ||
+ feature == Feature.TEXT_BLOCKS)
return true;
//Note: this is a backdoor which allows to optionally treat all features as 'preview' (for testing).
//When real preview features will be added, this method can be implemented to return 'true'
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java Thu Jun 06 12:24:44 2019 -0300
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java Thu Jun 06 12:24:44 2019 -0300
@@ -188,7 +188,8 @@
IMPORT_ON_DEMAND_OBSERVABLE_PACKAGES(JDK1_2, JDK8),
SWITCH_MULTIPLE_CASE_LABELS(JDK13, Fragments.FeatureMultipleCaseLabels, DiagKind.PLURAL),
SWITCH_RULE(JDK13, Fragments.FeatureSwitchRules, DiagKind.PLURAL),
- SWITCH_EXPRESSION(JDK13, Fragments.FeatureSwitchExpressions, DiagKind.PLURAL);
+ SWITCH_EXPRESSION(JDK13, Fragments.FeatureSwitchExpressions, DiagKind.PLURAL),
+ TEXT_BLOCKS(JDK13, Fragments.FeatureTextBlocks, DiagKind.PLURAL);
enum DiagKind {
NORMAL,
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavaTokenizer.java Thu Jun 06 12:24:44 2019 -0300
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavaTokenizer.java Thu Jun 06 12:24:44 2019 -0300
@@ -25,15 +25,22 @@
package com.sun.tools.javac.parser;
+import com.sun.tools.javac.code.Lint;
+import com.sun.tools.javac.code.Lint.LintCategory;
import com.sun.tools.javac.code.Preview;
import com.sun.tools.javac.code.Source;
import com.sun.tools.javac.code.Source.Feature;
import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle;
import com.sun.tools.javac.resources.CompilerProperties.Errors;
+import com.sun.tools.javac.resources.CompilerProperties.Warnings;
import com.sun.tools.javac.util.*;
-import com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag;
+import com.sun.tools.javac.util.JCDiagnostic.*;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.nio.CharBuffer;
+import java.util.HashSet;
+import java.util.Set;
import static com.sun.tools.javac.parser.Tokens.*;
import static com.sun.tools.javac.util.LayoutCharacters.*;
@@ -84,8 +91,21 @@
*/
protected UnicodeReader reader;
+ /** Should the string stripped of indentation?
+ */
+ protected boolean shouldStripIndent;
+
+ /** Should the string's escapes be translated?
+ */
+ protected boolean shouldTranslateEscapes;
+
protected ScannerFactory fac;
+ // The set of lint options currently in effect. It is initialized
+ // from the context, and then is set/reset as needed by Attr as it
+ // visits all the various parts of the trees during attribution.
+ protected Lint lint;
+
private static final boolean hexFloatsWork = hexFloatsWork();
private static boolean hexFloatsWork() {
try {
@@ -121,6 +141,7 @@
this.source = fac.source;
this.preview = fac.preview;
this.reader = reader;
+ this.lint = fac.lint;
}
protected void checkSourceLevel(int pos, Feature feature) {
@@ -150,6 +171,11 @@
errPos = pos;
}
+ protected void lexWarning(LintCategory lc, int pos, JCDiagnostic.Warning key) {
+ DiagnosticPosition dp = new SimpleDiagnosticPosition(pos) ;
+ log.warning(lc, dp, key);
+ }
+
/** Read next character in character or string literal and copy into sbuf.
*/
private void scanLitChar(int pos) {
@@ -200,6 +226,309 @@
}
}
+ /** Read next character in character or string literal and copy into sbuf
+ * without translating escapes. Used by text blocks to preflight verify
+ * escapes sequences.
+ */
+ private void scanLitCharRaw(int pos) {
+ if (reader.ch == '\\') {
+ if (reader.peekChar() == '\\' && !reader.isUnicode()) {
+ reader.skipChar();
+ reader.putChar('\\', false);
+ reader.putChar('\\', true);
+ } else {
+ reader.putChar('\\', true);
+ switch (reader.ch) {
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ char leadch = reader.ch;
+ reader.putChar(true);
+ if ('0' <= reader.ch && reader.ch <= '7') {
+ reader.putChar(true);
+ if (leadch <= '3' && '0' <= reader.ch && reader.ch <= '7') {
+ reader.putChar(true);
+ }
+ }
+ break;
+ // Effectively list of valid escape sequences.
+ case 'b':
+ case 't':
+ case 'n':
+ case 'f':
+ case 'r':
+ case '\'':
+ case '\"':
+ case '\\':
+ reader.putChar(true); break;
+ default:
+ lexError(reader.bp, Errors.IllegalEscChar);
+ }
+ }
+ } else if (reader.bp != reader.buflen) {
+ reader.putChar(true);
+ }
+ }
+
+ /** Interim access to String methods used to support text blocks.
+ * Required to handle bootstrapping with pre-text block jdks.
+ * Could be reworked in the 'next' jdk.
+ */
+ static class TextBlockSupport {
+ /** Reflection method to remove incidental indentation.
+ */
+ private static final Method stripIndent;
+
+ /** Reflection method to translate escape sequences.
+ */
+ private static final Method translateEscapes;
+
+ /** true if stripIndent and translateEscapes are available in the bootstrap jdk.
+ */
+ private static final boolean hasSupport;
+
+ /** Get a string method via refection or null if not available.
+ */
+ private static Method getStringMethodOrNull(String name) {
+ try {
+ return String.class.getMethod(name);
+ } catch (Exception ex) {
+ // Method not available, return null.
+ }
+ return null;
+ }
+
+ static {
+ // Get text block string methods.
+ stripIndent = getStringMethodOrNull("stripIndent");
+ translateEscapes = getStringMethodOrNull("translateEscapes");
+ // true if stripIndent and translateEscapes are available in the bootstrap jdk.
+ hasSupport = stripIndent != null && translateEscapes != null;
+ }
+
+ /** Return true if stripIndent and translateEscapes are available in the bootstrap jdk.
+ */
+ static boolean hasSupport() {
+ return hasSupport;
+ }
+
+ /** Return the leading whitespace count (indentation) of the line.
+ */
+ private static int indent(String line) {
+ return line.length() - line.stripLeading().length();
+ }
+
+ enum WhitespaceChecks {
+ INCONSISTENT,
+ TRAILING
+ };
+
+ /** Check that the use of white space in content is not problematic.
+ */
+ static Set<WhitespaceChecks> checkWhitespace(String string) {
+ // Start with empty result set.
+ Set<WhitespaceChecks> checks = new HashSet<>();
+ // No need to check empty strings.
+ if (string.isEmpty()) {
+ return checks;
+ }
+ // Maximum common indentation.
+ int outdent = 0;
+ // No need to check indentation if opting out (last line is empty.)
+ char lastChar = string.charAt(string.length() - 1);
+ boolean optOut = lastChar == '\n' || lastChar == '\r';
+ // Split string based at line terminators.
+ String[] lines = string.split("\\R");
+ int length = lines.length;
+ // Extract last line.
+ String lastLine = lines[length - 1];
+ if (!optOut) {
+ // Prime with the last line indentation (may be blank.)
+ outdent = indent(lastLine);
+ for (String line : lines) {
+ // Blanks lines have no influence (last line accounted for.)
+ if (!line.isBlank()) {
+ outdent = Integer.min(outdent, indent(line));
+ if (outdent == 0) {
+ break;
+ }
+ }
+ }
+ }
+ // Last line is representative.
+ String start = lastLine.substring(0, outdent);
+ for (String line : lines) {
+ // Fail if a line does not have the same indentation.
+ if (!line.isBlank() && !line.startsWith(start)) {
+ // Mix of different white space
+ checks.add(WhitespaceChecks.INCONSISTENT);
+ }
+ // Line has content even after indent is removed.
+ if (outdent < line.length()) {
+ // Is the last character a white space.
+ lastChar = line.charAt(line.length() - 1);
+ if (Character.isWhitespace(lastChar)) {
+ // Has trailing white space.
+ checks.add(WhitespaceChecks.TRAILING);
+ }
+ }
+ }
+ return checks;
+ }
+
+ /** Invoke String::stripIndent through reflection.
+ */
+ static String stripIndent(String string) {
+ try {
+ string = (String)stripIndent.invoke(string);
+ } catch (InvocationTargetException | IllegalAccessException ex) {
+ throw new RuntimeException(ex);
+ }
+ return string;
+ }
+
+ /** Invoke String::translateEscapes through reflection.
+ */
+ static String translateEscapes(String string) {
+ try {
+ string = (String)translateEscapes.invoke(string);
+ } catch (InvocationTargetException | IllegalAccessException ex) {
+ throw new RuntimeException(ex);
+ }
+ return string;
+ }
+ }
+
+ /** Test for EOLN.
+ */
+ private boolean isEOLN() {
+ return reader.ch == LF || reader.ch == CR;
+ }
+
+ /** Test for CRLF.
+ */
+ private boolean isCRLF() {
+ return reader.ch == CR && reader.peekChar() == LF;
+ }
+
+ /** Count and skip repeated occurances of the specified character.
+ */
+ private int countChar(char ch, int max) {
+ int count = 0;
+ for ( ; count < max && reader.bp < reader.buflen && reader.ch == ch; count++) {
+ reader.scanChar();
+ }
+ return count;
+ }
+
+ /** Scan a string literal or text block.
+ */
+ private void scanString(int pos) {
+ // Clear flags.
+ shouldStripIndent = false;
+ shouldTranslateEscapes = false;
+ // Check if text block string methods are present.
+ boolean hasTextBlockSupport = TextBlockSupport.hasSupport();
+ // Track the end of first line for error recovery.
+ int firstEOLN = -1;
+ // Attempt to scan for up to 3 double quotes.
+ int openCount = countChar('\"', 3);
+ switch (openCount) {
+ case 1: // Starting a string literal.
+ break;
+ case 2: // Starting an empty string literal.
+ // Start again but only consume one quote.
+ reader.reset(pos);
+ openCount = countChar('\"', 1);
+ break;
+ case 3: // Starting a text block.
+ // Check if preview feature is enabled for text blocks.
+ checkSourceLevel(pos, Feature.TEXT_BLOCKS);
+ // Only proceed if text block string methods are present.
+ if (hasTextBlockSupport) {
+ // Indicate that the final string should have incidental indentation removed.
+ shouldStripIndent = true;
+ // Verify the open delimiter sequence.
+ boolean hasOpenEOLN = false;
+ while (reader.bp < reader.buflen && Character.isWhitespace(reader.ch)) {
+ hasOpenEOLN = isEOLN();
+ if (hasOpenEOLN) {
+ break;
+ }
+ reader.scanChar();
+ }
+ // Error if the open delimiter sequence not is """<Whitespace>*<LineTerminator>.
+ if (!hasOpenEOLN) {
+ lexError(reader.bp, Errors.IllegalTextBlockOpen);
+ return;
+ }
+ // Skip line terminator.
+ int start = reader.bp;
+ if (isCRLF()) {
+ reader.scanChar();
+ }
+ reader.scanChar();
+ processLineTerminator(start, reader.bp);
+ } else {
+ // No text block string methods are present, so reset and treat like string literal.
+ reader.reset(pos);
+ openCount = countChar('\"', 1);
+ }
+ break;
+ }
+ // While characters are available.
+ while (reader.bp < reader.buflen) {
+ // If possible close delimiter sequence.
+ if (reader.ch == '\"') {
+ // Check to see if enough double quotes are present.
+ int closeCount = countChar('\"', openCount);
+ if (openCount == closeCount) {
+ // Good result.
+ tk = Tokens.TokenKind.STRINGLITERAL;
+ return;
+ }
+ // False alarm, add double quotes to string buffer.
+ reader.repeat('\"', closeCount);
+ } else if (isEOLN()) {
+ // Line terminator in string literal is an error.
+ // Fall out to unclosed string literal error.
+ if (openCount == 1) {
+ break;
+ }
+ // Add line terminator to string buffer.
+ int start = reader.bp;
+ if (isCRLF()) {
+ reader.scanChar();
+ }
+ reader.putChar('\n', true);
+ processLineTerminator(start, reader.bp);
+ // Record first line terminator for error recovery.
+ if (firstEOLN == -1) {
+ firstEOLN = reader.bp;
+ }
+ } else if (reader.ch == '\\') {
+ // Handle escape sequences.
+ if (hasTextBlockSupport) {
+ // Indicate that the final string should have escapes translated.
+ shouldTranslateEscapes = true;
+ // Validate escape sequence and add to string buffer.
+ scanLitCharRaw(pos);
+ } else {
+ // Translate escape sequence and add result to string buffer.
+ scanLitChar(pos);
+ }
+ } else {
+ // Add character to string buffer.
+ reader.putChar(true);
+ }
+ }
+ // String ended without close delimiter sequence.
+ lexError(pos, openCount == 1 ? Errors.UnclosedStrLit : Errors.UnclosedTextBlock);
+ if (firstEOLN != -1) {
+ // Reset recovery position to point after open delimiter sequence.
+ reader.reset(firstEOLN);
+ }
+ }
+
private void scanDigits(int pos, int digitRadix) {
char saveCh;
int savePos;
@@ -624,7 +953,7 @@
lexError(pos, Errors.EmptyCharLit);
reader.scanChar();
} else {
- if (reader.ch == CR || reader.ch == LF)
+ if (isEOLN())
lexError(pos, Errors.IllegalLineEndInCharLit);
scanLitChar(pos);
if (reader.ch == '\'') {
@@ -636,17 +965,9 @@
}
break loop;
case '\"':
- reader.scanChar();
- while (reader.ch != '\"' && reader.ch != CR && reader.ch != LF && reader.bp < reader.buflen)
- scanLitChar(pos);
- if (reader.ch == '\"') {
- tk = TokenKind.STRINGLITERAL;
- reader.scanChar();
- } else {
- lexError(pos, Errors.UnclosedStrLit);
- }
+ scanString(pos);
break loop;
- default:
+ default:
if (isSpecial(reader.ch)) {
scanOperator();
} else {
@@ -695,7 +1016,34 @@
switch (tk.tag) {
case DEFAULT: return new Token(tk, pos, endPos, comments);
case NAMED: return new NamedToken(tk, pos, endPos, name, comments);
- case STRING: return new StringToken(tk, pos, endPos, reader.chars(), comments);
+ case STRING: {
+ // Get characters from string buffer.
+ String string = reader.chars();
+ // If a text block.
+ if (shouldStripIndent) {
+ // Verify that the incidental indentation is consistent.
+ if (lint.isEnabled(LintCategory.TEXT_BLOCKS)) {
+ Set<TextBlockSupport.WhitespaceChecks> checks =
+ TextBlockSupport.checkWhitespace(string);
+ if (checks.contains(TextBlockSupport.WhitespaceChecks.INCONSISTENT)) {
+ lexWarning(LintCategory.TEXT_BLOCKS, pos,
+ Warnings.InconsistentWhiteSpaceIndentation);
+ }
+ if (checks.contains(TextBlockSupport.WhitespaceChecks.TRAILING)) {
+ lexWarning(LintCategory.TEXT_BLOCKS, pos,
+ Warnings.TrailingWhiteSpaceWillBeRemoved);
+ }
+ }
+ // Remove incidental indentation.
+ string = TextBlockSupport.stripIndent(string);
+ }
+ // Translate escape sequences if present.
+ if (shouldTranslateEscapes) {
+ string = TextBlockSupport.translateEscapes(string);
+ }
+ // Build string token.
+ return new StringToken(tk, pos, endPos, string, comments);
+ }
case NUMERIC: return new NumericToken(tk, pos, endPos, reader.chars(), radix, comments);
default: throw new AssertionError();
}
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/ScannerFactory.java Thu Jun 06 12:24:44 2019 -0300
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/ScannerFactory.java Thu Jun 06 12:24:44 2019 -0300
@@ -27,6 +27,7 @@
import java.nio.CharBuffer;
+import com.sun.tools.javac.code.Lint;
import com.sun.tools.javac.code.Preview;
import com.sun.tools.javac.code.Source;
import com.sun.tools.javac.util.Context;
@@ -59,6 +60,7 @@
final Source source;
final Preview preview;
final Tokens tokens;
+ final Lint lint;
/** Create a new scanner factory. */
protected ScannerFactory(Context context) {
@@ -68,6 +70,7 @@
this.source = Source.instance(context);
this.preview = Preview.instance(context);
this.tokens = Tokens.instance(context);
+ this.lint = Lint.instance(context);
}
public Scanner newScanner(CharSequence input, boolean keepDocComments) {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/UnicodeReader.java Thu Jun 06 12:24:44 2019 -0300
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/UnicodeReader.java Thu Jun 06 12:24:44 2019 -0300
@@ -154,6 +154,21 @@
return new String(sbuf, 0, sp);
}
+ /** Add 'count' copies of the character 'ch' to the string buffer.
+ */
+ protected void repeat(char ch, int count) {
+ for ( ; 0 < count; count--) {
+ putChar(ch, false);
+ }
+ }
+
+ /** Reset the scan buffer pointer to 'pos'.
+ */
+ protected void reset(int pos) {
+ bp = pos - 1;
+ scanChar();
+ }
+
/** Convert unicode escape; bp points to initial '\' character
* (Spec 3.3).
*/
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties Thu Jun 06 12:24:44 2019 -0300
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties Thu Jun 06 12:24:44 2019 -0300
@@ -623,6 +623,15 @@
compiler.err.illegal.line.end.in.char.lit=\
illegal line end in character literal
+compiler.err.illegal.text.block.open=\
+ illegal text block open delimiter sequence, missing line terminator
+
+compiler.warn.inconsistent.white.space.indentation=\
+ inconsistent white space indentation
+
+compiler.warn.trailing.white.space.will.be.removed=\
+ trailing white space will be removed
+
compiler.err.illegal.nonascii.digit=\
illegal non-ASCII digit
@@ -1244,6 +1253,9 @@
compiler.err.unclosed.str.lit=\
unclosed string literal
+compiler.err.unclosed.text.block=\
+ unclosed text block
+
# 0: string
compiler.err.unsupported.encoding=\
unsupported encoding: {0}
@@ -2859,6 +2871,9 @@
compiler.misc.feature.private.intf.methods=\
private interface methods
+compiler.misc.feature.text.blocks=\
+ text blocks
+
compiler.misc.feature.multiple.case.labels=\
multiple case labels
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties Thu Jun 06 12:24:44 2019 -0300
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties Thu Jun 06 12:24:44 2019 -0300
@@ -243,6 +243,9 @@
javac.opt.Xlint.desc.static=\
Warn about accessing a static member using an instance.
+javac.opt.Xlint.desc.text-blocks=\
+ Warn about inconsistent white space characters in text block indentation.
+
javac.opt.Xlint.desc.try=\
Warn about issues relating to use of try blocks (i.e. try-with-resources).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/langtools/tools/javac/TextBlockAPI.java Thu Jun 06 12:24:44 2019 -0300
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8223967
+ * @summary Unit tests for Text Block language changes
+ * @library /tools/lib
+ * @modules jdk.compiler/com.sun.tools.javac.api
+ * jdk.compiler/com.sun.tools.javac.main
+ * @build toolbox.ToolBox toolbox.JavacTask
+ * @run main TextBlockAPI
+ */
+
+import toolbox.JavacTask;
+import toolbox.JavaTask;
+import toolbox.Task;
+import toolbox.ToolBox;
+
+public class TextBlockAPI {
+ private static ToolBox TOOLBOX = new ToolBox();
+ private final static String JDK_VERSION = Integer.toString(Runtime.version().feature());
+
+ public static void main(String... args) {
+ test1();
+ test2();
+ test3();
+ test4();
+ }
+
+ /*
+ * Check that correct/incorrect syntax is properly detected
+ */
+ static void test1() {
+ for (String lineterminators : new String[] { "\n", "\r", "\r\n" })
+ for (String whitespace : new String[] { "", " ", "\t", "\u2002" })
+ for (String content : new String[] { "a", "ab", "abc", "\u2022", "*".repeat(1000), "*".repeat(10000) }) {
+ String code =
+ "public class CorrectTest {\n" +
+ " public static void main(String... args) {\n" +
+ " String xxx = " +
+ "\"\"\"" + whitespace + lineterminators +
+ content +
+ "\"\"\";\n" +
+ " }\n" +
+ "}\n";
+ compPass(code);
+ }
+ }
+
+ /*
+ * Check that use of \u0022 is properly detected
+ */
+ static void test2() {
+ compPass("public class UnicodeDelimiterTest {\n" +
+ " public static void main(String... args) {\n" +
+ " String xxx = \\u0022\\u0022\\u0022\nabc\n\\u0022\\u0022\\u0022;\n" +
+ " }\n" +
+ "}\n");
+ }
+
+ /*
+ * Check edge cases of text blocks as last token
+ */
+ static void test3() {
+ compFail("public class EndTest {\n" +
+ " public static void main(String... args) {\n" +
+ " String xxx = \"\"\"\nabc\"\"\"");
+ compFail("public class TwoQuoteClose {\n" +
+ " public static void main(String... args) {\n" +
+ " String xxx = \"\"\"\nabc\"\"");
+ compFail("public class OneQuoteClose {\n" +
+ " public static void main(String... args) {\n" +
+ " String xxx = \"\"\"\nabc\"");
+ compFail("public class NoClose {\n" +
+ " public static void main(String... args) {\n" +
+ " String xxx = \"\"\"\nabc");
+ compFail("public class ZeroTerminator {\n" +
+ " public static void main(String... args) {\n" +
+ " String xxx = \"\"\"\nabc\\u0000");
+ compFail("public class NonBreakingSpace {\n" +
+ " public static void main(String... args) {\n" +
+ " String xxx = \"\"\"\nabc\\u001A");
+ }
+
+ /*
+ * Check line terminator translation
+ */
+ static void test4() {
+ String[] terminators = new String[] { "\n", "\r\n", "\r" };
+ for (String terminator : terminators) {
+ String code = "public class LineTerminatorTest {" + terminator +
+ " public static void main(String... args) {" + terminator +
+ " String s =" + terminator +
+ "\"\"\"" + terminator +
+ "abc" + terminator +
+ "\"\"\";" + terminator +
+ " System.out.println(s.equals(\"abc\\n\"));" + terminator +
+ " }" + terminator +
+ "}" + terminator;
+ new JavacTask(TOOLBOX)
+ .sources(code)
+ .classpath(".")
+ .options("--enable-preview", "-source", JDK_VERSION, "-encoding", "utf8")
+ .run();
+ String output = new JavaTask(TOOLBOX)
+ .vmOptions("--enable-preview")
+ .classpath(".")
+ .classArgs("LineTerminatorTest")
+ .run()
+ .writeAll()
+ .getOutput(Task.OutputKind.STDOUT);
+
+ if (!output.contains("true")) {
+ throw new RuntimeException("Error detected");
+ }
+ }
+ }
+
+ /*
+ * Test source for successful compile.
+ */
+ static void compPass(String source) {
+ String output = new JavacTask(TOOLBOX)
+ .sources(source)
+ .classpath(".")
+ .options("--enable-preview", "-source", JDK_VERSION, "-encoding", "utf8")
+ .run()
+ .writeAll()
+ .getOutput(Task.OutputKind.DIRECT);
+
+ if (output.contains("compiler.err")) {
+ throw new RuntimeException("Error detected");
+ }
+ }
+
+ /*
+ * Test source for unsuccessful compile and specific error.
+ */
+ static void compFail(String source) {
+ String errors = new JavacTask(TOOLBOX)
+ .sources(source)
+ .classpath(".")
+ .options("-XDrawDiagnostics", "--enable-preview", "-source", JDK_VERSION, "-encoding", "utf8")
+ .run(Task.Expect.FAIL)
+ .writeAll()
+ .getOutput(Task.OutputKind.DIRECT);
+
+ if (!errors.contains("compiler.err")) {
+ throw new RuntimeException("No error detected");
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/langtools/tools/javac/TextBlockLang.java Thu Jun 06 12:24:44 2019 -0300
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8223967
+ * @summary Unit tests for Text Block language changes
+ * @compile --enable-preview -source ${jdk.version} -encoding utf8 TextBlockLang.java
+ * @run main/othervm --enable-preview TextBlockLang
+ */
+
+public class TextBlockLang {
+ public static void main(String... args) {
+ test1();
+ }
+
+ /*
+ * Test basic string functionality.
+ */
+ static void test1() {
+ EQ("""
+ """, "");
+ EQ("""
+ abc
+ """, "abc\n");
+ EQ("""
+
+ """, "\n");
+ EQ("""
+ "
+ """, "\"\n");
+ EQ("""
+ ""
+ """, "\"\"\n");
+ EQ("""
+ \"""
+ """, "\"\"\"\n");
+ EQ("""
+ "\""
+ """, "\"\"\"\n");
+ EQ("""
+ ""\"
+ """, "\"\"\"\n");
+ EQ("""
+ \r
+ """, "\r\n");
+ EQ("""
+ \u2022
+ """, "\u2022\n");
+ EQ("""
+ •
+ """, "\u2022\n");
+ LENGTH("""
+ abc
+ """, 4);
+ }
+
+
+ /*
+ * Raise an exception if the string is not the expected length.
+ */
+ static void LENGTH(String string, int length) {
+ if (string == null || string.length() != length) {
+ System.err.println("Failed LENGTH");
+ System.err.println(string + " " + length);
+ throw new RuntimeException("Failed LENGTH");
+ }
+ }
+
+ /*
+ * Raise an exception if the two input strings are not equal.
+ */
+ static void EQ(String input, String expected) {
+ if (input == null || expected == null || !expected.equals(input)) {
+ System.err.println("Failed EQ");
+ System.err.println();
+ System.err.println("Input:");
+ System.err.println(input.replaceAll(" ", "."));
+ System.err.println();
+ System.err.println("Expected:");
+ System.err.println(expected.replaceAll(" ", "."));
+ throw new RuntimeException();
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/langtools/tools/javac/diags/examples/TextBlockCloseDelimiter.java Thu Jun 06 12:24:44 2019 -0300
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+ // key: compiler.warn.preview.feature.use.plural
+ // key: compiler.misc.feature.text.blocks
+ // key: compiler.err.unclosed.text.block
+ // options: --enable-preview -source ${jdk.version} -Xlint:preview
+
+class TextBlockCloseDelimiter {
+ String m() {
+ return """
+ ;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/langtools/tools/javac/diags/examples/TextBlockOpenDelimiter.java Thu Jun 06 12:24:44 2019 -0300
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+ // key: compiler.warn.preview.feature.use.plural
+ // key: compiler.misc.feature.text.blocks
+ // key: compiler.err.illegal.text.block.open
+ // options: --enable-preview -source ${jdk.version} -Xlint:preview
+
+class TextBlockOpenDelimiter {
+ String m() {
+ return """xxxx
+ """;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/langtools/tools/javac/diags/examples/TextBlockWhitespace.java Thu Jun 06 12:24:44 2019 -0300
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+ // key: compiler.warn.preview.feature.use.plural
+ // key: compiler.misc.feature.text.blocks
+ // key: compiler.warn.inconsistent.white.space.indentation
+ // key: compiler.warn.trailing.white.space.will.be.removed
+ // options: --enable-preview -source ${jdk.version} -Xlint:preview,text-blocks
+
+class TextBlockWhitespace {
+ String m() {
+ return """
+\u0009\u0009\u0009\u0009tab indentation
+\u0020\u0020\u0020\u0020space indentation and trailing space\u0020
+\u0020\u0020\u0020\u0020""";
+ }
+}
--- a/test/langtools/tools/javap/WhitespaceTest.java Thu Jun 06 12:24:44 2019 -0300
+++ b/test/langtools/tools/javap/WhitespaceTest.java Thu Jun 06 12:24:44 2019 -0300
@@ -37,8 +37,8 @@
}
void run() throws Exception {
- test("-v", "java.lang.String");
- test("-XDtab:1", "-v", "java.lang.String");
+ test("-v", "java.lang.Object");
+ test("-XDtab:1", "-v", "java.lang.Object");
String testClasses = System.getProperty("test.classes");
for (int i = 10; i < 40; i++)