8201274: Launch Single-File Source-Code Programs
authorjjg
Thu, 07 Jun 2018 16:06:49 -0700
changeset 50453 f91927a2c8d3
parent 50452 ccb2c0d5da93
child 50454 d134badc8a42
8201274: Launch Single-File Source-Code Programs Reviewed-by: mcimadamore, jlahoda, ksrini, mchung, ihse, alanb
make/gensrc/Gensrc-jdk.compiler.gmk
make/langtools/build.properties
src/java.base/share/classes/sun/launcher/LauncherHelper.java
src/java.base/share/classes/sun/launcher/resources/launcher.properties
src/java.base/share/native/launcher/main.c
src/java.base/share/native/libjli/args.c
src/java.base/share/native/libjli/emessages.h
src/java.base/share/native/libjli/java.c
src/java.base/share/native/libjli/java.h
src/java.base/share/native/libjli/jli_util.c
src/java.base/share/native/libjli/jli_util.h
src/java.base/windows/native/libjli/cmdtoargs.c
src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/Main.java
src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher.properties
test/jdk/tools/launcher/SourceMode.java
test/langtools/tools/javac/diags/CheckResourceKeys.java
test/langtools/tools/javac/launcher/SourceLauncherTest.java
--- a/make/gensrc/Gensrc-jdk.compiler.gmk	Thu Jun 07 23:30:05 2018 +0200
+++ b/make/gensrc/Gensrc-jdk.compiler.gmk	Thu Jun 07 16:06:49 2018 -0700
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2014, 2018, 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
@@ -32,6 +32,7 @@
     $(JAVAC_VERSION)))
 
 $(eval $(call SetupParseProperties,PARSE_PROPERTIES, \
-	com/sun/tools/javac/resources/compiler.properties))
+	com/sun/tools/javac/resources/compiler.properties \
+	com/sun/tools/javac/resources/launcher.properties))
 
 all: $(COMPILE_PROPERTIES) $(PARSE_PROPERTIES)
--- a/make/langtools/build.properties	Thu Jun 07 23:30:05 2018 +0200
+++ b/make/langtools/build.properties	Thu Jun 07 16:06:49 2018 -0700
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2007, 2018, 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
@@ -38,7 +38,8 @@
                jdk.jshell
 
 langtools.resource.includes = \
-        com/sun/tools/javac/resources/compiler.properties
+        com/sun/tools/javac/resources/compiler.properties \
+        com/sun/tools/javac/resources/launcher.properties
 
 # Version info -- override as needed
 jdk.version = 9
--- a/src/java.base/share/classes/sun/launcher/LauncherHelper.java	Thu Jun 07 23:30:05 2018 +0200
+++ b/src/java.base/share/classes/sun/launcher/LauncherHelper.java	Thu Jun 07 16:06:49 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 2018, 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
@@ -502,12 +502,13 @@
     }
 
     // From src/share/bin/java.c:
-    //   enum LaunchMode { LM_UNKNOWN = 0, LM_CLASS, LM_JAR, LM_MODULE }
+    //   enum LaunchMode { LM_UNKNOWN = 0, LM_CLASS, LM_JAR, LM_MODULE, LM_SOURCE }
 
     private static final int LM_UNKNOWN = 0;
     private static final int LM_CLASS   = 1;
     private static final int LM_JAR     = 2;
     private static final int LM_MODULE  = 3;
+    private static final int LM_SOURCE  = 4;
 
     static void abort(Throwable t, String msgKey, Object... args) {
         if (msgKey != null) {
@@ -538,13 +539,21 @@
      *
      * @return the application's main class
      */
+    @SuppressWarnings("fallthrough")
     public static Class<?> checkAndLoadMain(boolean printToStderr,
                                             int mode,
                                             String what) {
         initOutput(printToStderr);
 
-        Class<?> mainClass = (mode == LM_MODULE) ? loadModuleMainClass(what)
-                                                 : loadMainClass(mode, what);
+        Class<?> mainClass = null;
+        switch (mode) {
+            case LM_MODULE: case LM_SOURCE:
+                mainClass = loadModuleMainClass(what);
+                break;
+            default:
+                mainClass = loadMainClass(mode, what);
+                break;
+        }
 
         // record the real main class for UI purposes
         // neither method above can return null, they will abort()
--- a/src/java.base/share/classes/sun/launcher/resources/launcher.properties	Thu Jun 07 23:30:05 2018 +0200
+++ b/src/java.base/share/classes/sun/launcher/resources/launcher.properties	Thu Jun 07 16:06:49 2018 -0700
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2007, 2017, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2007, 2018, 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
@@ -25,13 +25,17 @@
 
 # Translators please note do not translate the options themselves
 java.launcher.opt.header  =   Usage: {0} [options] <mainclass> [args...]\n\
-\           (to execute a class)\n   or  {0} [options] -jar <jarfile> [args...]\n\
+\           (to execute a class)\n\
+\   or  {0} [options] -jar <jarfile> [args...]\n\
 \           (to execute a jar file)\n\
 \   or  {0} [options] -m <module>[/<mainclass>] [args...]\n\
 \       {0} [options] --module <module>[/<mainclass>] [args...]\n\
-\           (to execute the main class in a module)\n\n\
-\ Arguments following the main class, -jar <jarfile>, -m or --module\n\
-\ <module>/<mainclass> are passed as the arguments to main class.\n\n\
+\           (to execute the main class in a module)\n\
+\   or  {0} [options] <sourcefile> [args]\n\
+\           (to execute a single source-file program)\n\n\
+\ Arguments following the main class, source file, -jar <jarfile>,\n\
+\ -m or --module <module>/<mainclass> are passed as the arguments to\n\
+\ main class.\n\n\
 \ where options include:\n\n
 
 java.launcher.opt.vmselect   =\    {0}\t  to select the "{1}" VM\n
@@ -114,7 +118,7 @@
 \    -disable-@files\n\
 \                  prevent further argument file expansion\n\
 \    --enable-preview\n\
-\                  allow classes to depend on preview features of this release
+\                  allow classes to depend on preview features of this release\n\
 \To specify an argument for a long option, you can use --<name>=<value> or\n\
 \--<name> <value>.\n
 
@@ -176,7 +180,9 @@
 \    --patch-module <module>=<file>({0}<file>)*\n\
 \                      override or augment a module with classes and resources\n\
 \                      in JAR files or directories.\n\
-\    --disable-@files  disable further argument file expansion\n\n\
+\    --disable-@files  disable further argument file expansion\n\
+\    --source <version>\n\
+\                      set the version of the source in source-file mode.\n\n\
 These extra options are subject to change without notice.\n
 
 # Translators please note do not translate the options themselves
--- a/src/java.base/share/native/launcher/main.c	Thu Jun 07 23:30:05 2018 +0200
+++ b/src/java.base/share/native/launcher/main.c	Thu Jun 07 16:06:49 2018 -0700
@@ -183,7 +183,7 @@
         }
         // Iterate the rest of command line
         for (i = 1; i < argc; i++) {
-            JLI_List argsInFile = JLI_PreprocessArg(argv[i]);
+            JLI_List argsInFile = JLI_PreprocessArg(argv[i], JNI_TRUE);
             if (NULL == argsInFile) {
                 JLI_List_add(args, JLI_StringDup(argv[i]));
             } else {
--- a/src/java.base/share/native/libjli/args.c	Thu Jun 07 23:30:05 2018 +0200
+++ b/src/java.base/share/native/libjli/args.c	Thu Jun 07 16:06:49 2018 -0700
@@ -79,6 +79,11 @@
 static jboolean stopExpansion = JNI_FALSE;
 static jboolean relaunch = JNI_FALSE;
 
+/*
+ * Prototypes for internal functions.
+ */
+static jboolean expand(JLI_List args, const char *str, const char *var_name);
+
 JNIEXPORT void JNICALL
 JLI_InitArgProcessing(jboolean hasJavaArgs, jboolean disableArgFile) {
     // No expansion for relaunch
@@ -376,9 +381,22 @@
     return rv;
 }
 
+/*
+ * expand a string into a list of words separated by whitespace.
+ */
+static JLI_List expandArg(const char *arg) {
+    JLI_List rv;
+
+    /* arbitrarily pick 8, seems to be a reasonable number of arguments */
+    rv = JLI_List_new(8);
+
+    expand(rv, arg, NULL);
+
+    return rv;
+}
+
 JNIEXPORT JLI_List JNICALL
-JLI_PreprocessArg(const char *arg)
-{
+JLI_PreprocessArg(const char *arg, jboolean expandSourceOpt) {
     JLI_List rv;
 
     if (firstAppArgIndex > 0) {
@@ -392,6 +410,12 @@
         return NULL;
     }
 
+    if (expandSourceOpt
+            && JLI_StrCCmp(arg, "--source") == 0
+            && JLI_StrChr(arg, ' ') != NULL) {
+        return expandArg(arg);
+    }
+
     if (arg[0] != '@') {
         checkArg(arg);
         return NULL;
@@ -435,9 +459,6 @@
 JNIEXPORT jboolean JNICALL
 JLI_AddArgsFromEnvVar(JLI_List args, const char *var_name) {
     char *env = getenv(var_name);
-    char *p, *arg;
-    char quote;
-    JLI_List argsInFile;
 
     if (firstAppArgIndex == 0) {
         // Not 'java', return
@@ -453,44 +474,64 @@
     }
 
     JLI_ReportMessage(ARG_INFO_ENVVAR, var_name, env);
+    return expand(args, env, var_name);
+}
+
+/*
+ * Expand a string into a list of args.
+ * If the string is the result of looking up an environment variable,
+ * var_name should be set to the name of that environment variable,
+ * for use if needed in error messages.
+ */
+
+static jboolean expand(JLI_List args, const char *str, const char *var_name) {
+    jboolean inEnvVar = (var_name != NULL);
+
+    char *p, *arg;
+    char quote;
+    JLI_List argsInFile;
 
     // This is retained until the process terminates as it is saved as the args
-    p = JLI_MemAlloc(JLI_StrLen(env) + 1);
-    while (*env != '\0') {
-        while (*env != '\0' && isspace(*env)) {
-            env++;
+    p = JLI_MemAlloc(JLI_StrLen(str) + 1);
+    while (*str != '\0') {
+        while (*str != '\0' && isspace(*str)) {
+            str++;
         }
 
         // Trailing space
-        if (*env == '\0') {
+        if (*str == '\0') {
             break;
         }
 
         arg = p;
-        while (*env != '\0' && !isspace(*env)) {
-            if (*env == '"' || *env == '\'') {
-                quote = *env++;
-                while (*env != quote && *env != '\0') {
-                    *p++ = *env++;
+        while (*str != '\0' && !isspace(*str)) {
+            if (inEnvVar && (*str == '"' || *str == '\'')) {
+                quote = *str++;
+                while (*str != quote && *str != '\0') {
+                    *p++ = *str++;
                 }
 
-                if (*env == '\0') {
+                if (*str == '\0') {
                     JLI_ReportMessage(ARG_ERROR8, var_name);
                     exit(1);
                 }
-                env++;
+                str++;
             } else {
-                *p++ = *env++;
+                *p++ = *str++;
             }
         }
 
         *p++ = '\0';
 
-        argsInFile = JLI_PreprocessArg(arg);
+        argsInFile = JLI_PreprocessArg(arg, JNI_FALSE);
 
         if (NULL == argsInFile) {
             if (isTerminalOpt(arg)) {
-                JLI_ReportMessage(ARG_ERROR9, arg, var_name);
+                if (inEnvVar) {
+                    JLI_ReportMessage(ARG_ERROR9, arg, var_name);
+                } else {
+                    JLI_ReportMessage(ARG_ERROR15, arg);
+                }
                 exit(1);
             }
             JLI_List_add(args, arg);
@@ -501,7 +542,11 @@
             for (idx = 0; idx < cnt; idx++) {
                 arg = argsInFile->elements[idx];
                 if (isTerminalOpt(arg)) {
-                    JLI_ReportMessage(ARG_ERROR10, arg, argFile, var_name);
+                    if (inEnvVar) {
+                        JLI_ReportMessage(ARG_ERROR10, arg, argFile, var_name);
+                    } else {
+                        JLI_ReportMessage(ARG_ERROR16, arg, argFile);
+                    }
                     exit(1);
                 }
                 JLI_List_add(args, arg);
@@ -517,11 +562,15 @@
          * caught now.
          */
         if (firstAppArgIndex != NOT_FOUND) {
-            JLI_ReportMessage(ARG_ERROR11, var_name);
+            if (inEnvVar) {
+                JLI_ReportMessage(ARG_ERROR11, var_name);
+            } else {
+                JLI_ReportMessage(ARG_ERROR17);
+            }
             exit(1);
         }
 
-        assert (*env == '\0' || isspace(*env));
+        assert (*str == '\0' || isspace(*str));
     }
 
     return JNI_TRUE;
@@ -642,7 +691,7 @@
 
     if (argc > 1) {
         for (i = 0; i < argc; i++) {
-            JLI_List tokens = JLI_PreprocessArg(argv[i]);
+            JLI_List tokens = JLI_PreprocessArg(argv[i], JNI_FALSE);
             if (NULL != tokens) {
                 for (j = 0; j < tokens->size; j++) {
                     printf("Token[%lu]: <%s>\n", (unsigned long) j, tokens->elements[j]);
--- a/src/java.base/share/native/libjli/emessages.h	Thu Jun 07 23:30:05 2018 +0200
+++ b/src/java.base/share/native/libjli/emessages.h	Thu Jun 07 16:06:49 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2005, 2018, 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
@@ -51,6 +51,11 @@
 #define ARG_ERROR10     "Error: Option %s in %s is not allowed in environment variable %s"
 #define ARG_ERROR11     "Error: Cannot specify main class in environment variable %s"
 #define ARG_ERROR12     "Error: %s requires module name"
+#define ARG_ERROR13     "Error: %s requires source version"
+#define ARG_ERROR14     "Error: Option %s is not allowed with --source"
+#define ARG_ERROR15     "Error: Option %s is not allowed in this context"
+#define ARG_ERROR16     "Error: Option %s in %s is not allowed in this context"
+#define ARG_ERROR17     "Error: Cannot specify main class in this context"
 
 #define JVM_ERROR1      "Error: Could not create the Java Virtual Machine.\n" GEN_ERROR
 #define JVM_ERROR2      "Error: Could not detach main thread.\n" JNI_ERROR
--- a/src/java.base/share/native/libjli/java.c	Thu Jun 07 23:30:05 2018 +0200
+++ b/src/java.base/share/native/libjli/java.c	Thu Jun 07 16:06:49 2018 -0700
@@ -172,6 +172,9 @@
 static void FreeKnownVMs();
 static jboolean IsWildCardEnabled();
 
+
+#define SOURCE_LAUNCHER_MAIN_ENTRY "jdk.compiler/com.sun.tools.javac.launcher.Main"
+
 /*
  * This reports error.  VM will not be created and no usage is printed.
  */
@@ -214,7 +217,7 @@
  * Entry point.
  */
 JNIEXPORT int JNICALL
-JLI_Launch(int argc, char ** argv,              /* main argc, argc */
+JLI_Launch(int argc, char ** argv,              /* main argc, argv */
         int jargc, const char** jargv,          /* java args */
         int appclassc, const char** appclassv,  /* app classpath */
         const char* fullversion,                /* full version defined */
@@ -317,8 +320,7 @@
     /* Parse command line options; if the return value of
      * ParseArguments is false, the program should exit.
      */
-    if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath))
-    {
+    if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath)) {
         return(ret);
     }
 
@@ -585,7 +587,8 @@
     return IsClassPathOption(name) ||
            IsLauncherMainOption(name) ||
            JLI_StrCmp(name, "--describe-module") == 0 ||
-           JLI_StrCmp(name, "-d") == 0;
+           JLI_StrCmp(name, "-d") == 0 ||
+           JLI_StrCmp(name, "--source") == 0;
 }
 
 /*
@@ -627,6 +630,29 @@
 }
 
 /*
+ * Check if it is OK to set the mode.
+ * If the mode was previously set, and should not be changed,
+ * a fatal error is reported.
+ */
+static int
+checkMode(int mode, int newMode, const char *arg) {
+    if (mode == LM_SOURCE) {
+        JLI_ReportErrorMessage(ARG_ERROR14, arg);
+        exit(1);
+    }
+    return newMode;
+}
+
+/*
+ * Test if an arg identifies a source file.
+ */
+jboolean
+IsSourceFile(const char *arg) {
+    struct stat st;
+    return (JLI_HasSuffix(arg, ".java") && stat(arg, &st) == 0);
+}
+
+/*
  * Checks the command line options to find which JVM type was
  * specified.  If no command line option was given for the JVM type,
  * the default type is used.  The environment variable
@@ -1230,7 +1256,8 @@
         value = equals+1;
         if (JLI_StrCCmp(arg, "--describe-module=") == 0 ||
             JLI_StrCCmp(arg, "--module=") == 0 ||
-            JLI_StrCCmp(arg, "--class-path=") == 0) {
+            JLI_StrCCmp(arg, "--class-path=") == 0||
+            JLI_StrCCmp(arg, "--source=") == 0) {
             kind = LAUNCHER_OPTION_WITH_ARGUMENT;
         } else {
             kind = VM_LONG_OPTION;
@@ -1274,17 +1301,28 @@
  */
         if (JLI_StrCmp(arg, "-jar") == 0) {
             ARG_CHECK(argc, ARG_ERROR2, arg);
-            mode = LM_JAR;
+            mode = checkMode(mode, LM_JAR, arg);
         } else if (JLI_StrCmp(arg, "--module") == 0 ||
                    JLI_StrCCmp(arg, "--module=") == 0 ||
                    JLI_StrCmp(arg, "-m") == 0) {
             REPORT_ERROR (has_arg, ARG_ERROR5, arg);
             SetMainModule(value);
-            mode = LM_MODULE;
+            mode = checkMode(mode, LM_MODULE, arg);
             if (has_arg) {
                *pwhat = value;
                 break;
             }
+        } else if (JLI_StrCmp(arg, "--source") == 0 ||
+                   JLI_StrCCmp(arg, "--source=") == 0) {
+            REPORT_ERROR (has_arg, ARG_ERROR13, arg);
+            mode = LM_SOURCE;
+            if (has_arg) {
+                const char *prop = "-Djdk.internal.javac.source=";
+                size_t size = JLI_StrLen(prop) + JLI_StrLen(value) + 1;
+                char *propValue = (char *)JLI_MemAlloc(size);
+                JLI_Snprintf(propValue, size, "%s%s", prop, value);
+                AddOption(propValue, NULL);
+            }
         } else if (JLI_StrCmp(arg, "--class-path") == 0 ||
                    JLI_StrCCmp(arg, "--class-path=") == 0 ||
                    JLI_StrCmp(arg, "-classpath") == 0 ||
@@ -1435,12 +1473,25 @@
         if (!_have_classpath) {
             SetClassPath(".");
         }
-        mode = LM_CLASS;
+        mode = IsSourceFile(arg) ? LM_SOURCE : LM_CLASS;
+    } else if (mode == LM_CLASS && IsSourceFile(arg)) {
+        /* override LM_CLASS mode if given a source file */
+        mode = LM_SOURCE;
     }
 
-    if (argc >= 0) {
-        *pargc = argc;
-        *pargv = argv;
+    if (mode == LM_SOURCE) {
+        AddOption("--add-modules=ALL-DEFAULT", NULL);
+        *pwhat = SOURCE_LAUNCHER_MAIN_ENTRY;
+        // adjust (argc, argv) so that the name of the source file
+        // is included in the args passed to the source launcher
+        // main entry class
+        *pargc = argc + 1;
+        *pargv = argv - 1;
+    } else {
+        if (argc >= 0) {
+            *pargc = argc;
+            *pargv = argv;
+        }
     }
 
     *pmode = mode;
--- a/src/java.base/share/native/libjli/java.h	Thu Jun 07 23:30:05 2018 +0200
+++ b/src/java.base/share/native/libjli/java.h	Thu Jun 07 16:06:49 2018 -0700
@@ -230,11 +230,12 @@
     LM_UNKNOWN = 0,
     LM_CLASS,
     LM_JAR,
-    LM_MODULE
+    LM_MODULE,
+    LM_SOURCE
 };
 
 static const char *launchModeNames[]
-    = { "Unknown", "Main class", "JAR file", "Module" };
+    = { "Unknown", "Main class", "JAR file", "Module", "Source" };
 
 typedef struct {
     int    argc;
--- a/src/java.base/share/native/libjli/jli_util.c	Thu Jun 07 23:30:05 2018 +0200
+++ b/src/java.base/share/native/libjli/jli_util.c	Thu Jun 07 16:06:49 2018 -0700
@@ -84,6 +84,16 @@
     free(ptr);
 }
 
+jboolean
+JLI_HasSuffix(const char *s1, const char *s2)
+{
+    char *p = JLI_StrRChr(s1, '.');
+    if (p == NULL || *p == '\0') {
+        return JNI_FALSE;
+    }
+    return (JLI_StrCaseCmp(p, s2) == 0);
+}
+
 /*
  * debug helpers we use
  */
--- a/src/java.base/share/native/libjli/jli_util.h	Thu Jun 07 23:30:05 2018 +0200
+++ b/src/java.base/share/native/libjli/jli_util.h	Thu Jun 07 16:06:49 2018 -0700
@@ -51,7 +51,8 @@
 JNIEXPORT void JNICALL
 JLI_MemFree(void *ptr);
 
-int   JLI_StrCCmp(const char *s1, const char* s2);
+int   JLI_StrCCmp(const char *s1, const char *s2);
+jboolean   JLI_HasSuffix(const char *s1, const char *s2);
 
 typedef struct {
     char *arg;
@@ -158,7 +159,7 @@
 JLI_InitArgProcessing(jboolean hasJavaArgs, jboolean disableArgFile);
 
 JNIEXPORT JLI_List JNICALL
-JLI_PreprocessArg(const char *arg);
+JLI_PreprocessArg(const char *arg, jboolean expandSourceOpt);
 
 JNIEXPORT jboolean JNICALL
 JLI_AddArgsFromEnvVar(JLI_List args, const char *var_name);
--- a/src/java.base/windows/native/libjli/cmdtoargs.c	Thu Jun 07 23:30:05 2018 +0200
+++ b/src/java.base/windows/native/libjli/cmdtoargs.c	Thu Jun 07 16:06:49 2018 -0700
@@ -246,7 +246,7 @@
     // iterate through rest of command line
     while (src != NULL) {
         src = next_arg(src, arg, &wildcard);
-        argsInFile = JLI_PreprocessArg(arg);
+        argsInFile = JLI_PreprocessArg(arg, JNI_TRUE);
         if (argsInFile != NULL) {
             // resize to accommodate another Arg
             cnt = argsInFile->size;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/Main.java	Thu Jun 07 16:06:49 2018 -0700
@@ -0,0 +1,550 @@
+/*
+ * Copyright (c) 2018, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.sun.tools.javac.launcher;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.NestingKind;
+import javax.lang.model.element.TypeElement;
+import javax.tools.FileObject;
+import javax.tools.ForwardingJavaFileManager;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileManager.Location;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+
+import com.sun.source.util.JavacTask;
+import com.sun.source.util.TaskEvent;
+import com.sun.source.util.TaskListener;
+import com.sun.tools.javac.api.JavacTool;
+import com.sun.tools.javac.code.Source;
+import com.sun.tools.javac.resources.LauncherProperties.Errors;
+import com.sun.tools.javac.util.JCDiagnostic.Error;
+
+import jdk.internal.misc.VM;
+
+import static javax.tools.JavaFileObject.Kind.SOURCE;
+
+/**
+ * Compiles a source file, and executes the main method it contains.
+ *
+ * <p><b>This is NOT part of any supported API.
+ * If you write code that depends on this, you do so at your own
+ * risk.  This code and its internal interfaces are subject to change
+ * or deletion without notice.</b></p>
+ */
+public class Main {
+    /**
+     * An exception used to report errors.
+     */
+    public class Fault extends Exception {
+        private static final long serialVersionUID = 1L;
+        Fault(Error error) {
+            super(Main.this.getMessage(error));
+        }
+    }
+
+    /**
+     * Compiles a source file, and executes the main method it contains.
+     *
+     * <p>This is normally invoked from the Java launcher, either when
+     * the {@code --source} option is used, or when the first argument
+     * that is not part of a runtime option ends in {@code .java}.
+     *
+     * <p>The first entry in the {@code args} array is the source file
+     * to be compiled and run; all subsequent entries are passed as
+     * arguments to the main method of the first class found in the file.
+     *
+     * <p>If any problem occurs before executing the main class, it will
+     * be reported to the standard error stream, and the the JVM will be
+     * terminated by calling {@code System.exit} with a non-zero return code.
+     *
+     * @param args the arguments
+     * @throws Throwable if the main method throws an exception
+     */
+    public static void main(String... args) throws Throwable {
+        try {
+            new Main(System.err).run(VM.getRuntimeArguments(), args);
+        } catch (Fault f) {
+            System.err.println(f.getMessage());
+            System.exit(1);
+        } catch (InvocationTargetException e) {
+            // leave VM to handle the stacktrace, in the standard manner
+            throw e.getTargetException();
+        }
+    }
+
+    /** Stream for reporting errors, such as compilation errors. */
+    private PrintWriter out;
+
+    /**
+     * Creates an instance of this class, providing a stream to which to report
+     * any errors.
+     *
+     * @param out the stream
+     */
+    public Main(PrintStream out) {
+        this(new PrintWriter(new OutputStreamWriter(out), true));
+    }
+
+    /**
+     * Creates an instance of this class, providing a stream to which to report
+     * any errors.
+     *
+     * @param out the stream
+     */
+    public Main(PrintWriter out) {
+        this.out = out;
+    }
+
+    /**
+     * Compiles a source file, and executes the main method it contains.
+     *
+     * <p>The first entry in the {@code args} array is the source file
+     * to be compiled and run; all subsequent entries are passed as
+     * arguments to the main method of the first class found in the file.
+     *
+     * <p>Options for {@code javac} are obtained by filtering the runtime arguments.
+     *
+     * <p>If the main method throws an exception, it will be propagated in an
+     * {@code InvocationTargetException}. In that case, the stack trace of the
+     * target exception will be truncated such that the main method will be the
+     * last entry on the stack. In other words, the stack frames leading up to the
+     * invocation of the main method will be removed.
+     *
+     * @param runtimeArgs the runtime arguments
+     * @param args the arguments
+     * @throws Fault if a problem is detected before the main method can be executed
+     * @throws InvocationTargetException if the main method throws an exception
+     */
+    public void run(String[] runtimeArgs, String[] args) throws Fault, InvocationTargetException {
+        Path file = getFile(args);
+
+        Context context = new Context();
+        String mainClassName = compile(file, getJavacOpts(runtimeArgs), context);
+
+        String[] appArgs = Arrays.copyOfRange(args, 1, args.length);
+        execute(mainClassName, appArgs, context);
+    }
+
+    /**
+     * Returns the path for the filename found in the first of an array of arguments.
+     *
+     * @param args the array
+     * @return the path
+     * @throws Fault if there is a problem determining the path, or if the file does not exist
+     */
+    private Path getFile(String[] args) throws Fault {
+        if (args.length == 0) {
+            // should not happen when invoked from launcher
+            throw new Fault(Errors.NoArgs);
+        }
+        Path file;
+        try {
+            file = Paths.get(args[0]);
+        } catch (InvalidPathException e) {
+            throw new Fault(Errors.InvalidFilename(args[0]));
+        }
+        if (!Files.exists(file)) {
+            // should not happen when invoked from launcher
+            throw new Fault(Errors.FileNotFound(file));
+        }
+        return file;
+    }
+
+    /**
+     * Reads a source file, ignoring the first line if it is not a Java source file and
+     * it begins with {@code #!}.
+     *
+     * <p>If it is not a Java source file, and if the first two bytes are {@code #!},
+     * indicating a "magic number" of an executable text file, the rest of the first line
+     * up to but not including the newline is ignored. All characters after the first two are
+     * read in the {@link Charset#defaultCharset default platform encoding}.
+     *
+     * @param file the file
+     * @return a file object containing the content of the file
+     * @throws Fault if an error occurs while reading the file
+     */
+    private JavaFileObject readFile(Path file) throws Fault {
+        // use a BufferedInputStream to guarantee that we can use mark and reset.
+        try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(file))) {
+            boolean ignoreFirstLine;
+            if (file.getFileName().toString().endsWith(".java")) {
+                ignoreFirstLine = false;
+            } else {
+                in.mark(2);
+                ignoreFirstLine = (in.read() == '#') && (in.read() == '!');
+                if (!ignoreFirstLine) {
+                    in.reset();
+                }
+            }
+            try (BufferedReader r = new BufferedReader(new InputStreamReader(in, Charset.defaultCharset()))) {
+                StringBuilder sb = new StringBuilder();
+                if (ignoreFirstLine) {
+                    r.readLine();
+                    sb.append("\n"); // preserve line numbers
+                }
+                char[] buf = new char[1024];
+                int n;
+                while ((n = r.read(buf, 0, buf.length)) != -1)  {
+                    sb.append(buf, 0, n);
+                }
+                return new SimpleJavaFileObject(file.toUri(), SOURCE) {
+                    @Override
+                    public String getName() {
+                        return file.toString();
+                    }
+                    @Override
+                    public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+                        return sb;
+                    }
+                    @Override
+                    public boolean isNameCompatible(String simpleName, JavaFileObject.Kind kind) {
+                        // reject package-info and module-info; accept other names
+                        return (kind == JavaFileObject.Kind.SOURCE)
+                                && SourceVersion.isIdentifier(simpleName);
+                    }
+                    @Override
+                    public String toString() {
+                        return "JavacSourceLauncher[" + file + "]";
+                    }
+                };
+            }
+        } catch (IOException e) {
+            throw new Fault(Errors.CantReadFile(file, e));
+        }
+    }
+
+    /**
+     * Returns the subset of the runtime arguments that are relevant to {@code javac}.
+     * Generally, the relevant options are those for setting paths and for configuring the
+     * module system.
+     *
+     * @param runtimeArgs the runtime arguments
+     * @return the subset of the runtime arguments
+     **/
+    private List<String> getJavacOpts(String... runtimeArgs) throws Fault {
+        List<String> javacOpts = new ArrayList<>();
+
+        String sourceOpt = System.getProperty("jdk.internal.javac.source");
+        if (sourceOpt != null) {
+            Source source = Source.lookup(sourceOpt);
+            if (source == null) {
+                throw new Fault(Errors.InvalidValueForSource(sourceOpt));
+            }
+            javacOpts.addAll(List.of("--release", sourceOpt));
+        }
+
+        for (int i = 0; i < runtimeArgs.length; i++) {
+            String arg = runtimeArgs[i];
+            String opt = arg, value = null;
+            if (arg.startsWith("--")) {
+                int eq = arg.indexOf('=');
+                if (eq > 0) {
+                    opt = arg.substring(0, eq);
+                    value = arg.substring(eq + 1);
+                }
+            }
+            switch (opt) {
+                // The following options all expect a value, either in the following
+                // position, or after '=', for options beginning "--".
+                case "--class-path": case "-classpath": case "-cp":
+                case "--module-path": case "-p":
+                case "--add-exports":
+                case "--add-modules":
+                case "--limit-modules":
+                case "--patch-module":
+                case "--upgrade-module-path":
+                    if (value == null) {
+                        if (i== runtimeArgs.length - 1) {
+                            // should not happen when invoked from launcher
+                            throw new Fault(Errors.NoValueForOption(opt));
+                        }
+                        value = runtimeArgs[++i];
+                    }
+                    if (opt.equals("--add-modules") && value.equals("ALL-DEFAULT")) {
+                        // this option is only supported at run time;
+                        // it is not required or supported at compile time
+                        break;
+                    }
+                    javacOpts.add(opt);
+                    javacOpts.add(value);
+                    break;
+                case "--enable-preview":
+                    javacOpts.add(opt);
+                    if (sourceOpt == null) {
+                        throw new Fault(Errors.EnablePreviewRequiresSource);
+                    }
+                    break;
+                default:
+                    // ignore all other runtime args
+            }
+        }
+
+        // add implicit options
+        javacOpts.add("-proc:none");
+
+        return javacOpts;
+    }
+
+    /**
+     * Compiles a source file, placing the class files in a map in memory.
+     * Any messages generated during compilation will be written to the stream
+     * provided when this object was created.
+     *
+     * @param file the source file
+     * @param javacOpts compilation options for {@code javac}
+     * @param context the context for the compilation
+     * @return the name of the first class found in the source file
+     * @throws Fault if any compilation errors occur, or if no class was found
+     */
+    private String compile(Path file, List<String> javacOpts, Context context) throws Fault {
+        JavaFileObject fo = readFile(file);
+
+        JavacTool javaCompiler = JavacTool.create();
+        StandardJavaFileManager stdFileMgr = javaCompiler.getStandardFileManager(null, null, null);
+        try {
+            stdFileMgr.setLocation(StandardLocation.SOURCE_PATH, Collections.emptyList());
+        } catch (IOException e) {
+            throw new java.lang.Error("unexpected exception from file manager", e);
+        }
+        JavaFileManager fm = context.getFileManager(stdFileMgr);
+        JavacTask t = javaCompiler.getTask(out, fm, null, javacOpts, null, List.of(fo));
+        MainClassListener l = new MainClassListener(t);
+        Boolean ok = t.call();
+        if (!ok) {
+            throw new Fault(Errors.CompilationFailed);
+        }
+        if (l.mainClass == null) {
+            throw new Fault(Errors.NoClass);
+        }
+        String mainClassName = l.mainClass.getQualifiedName().toString();
+        return mainClassName;
+    }
+
+    /**
+     * Invokes the {@code main} method of a specified class, using a class loader that
+     * will load recently compiled classes from memory.
+     *
+     * @param mainClassName the class to be executed
+     * @param appArgs the arguments for the {@code main} method
+     * @param context the context for the class to be executed
+     * @throws Fault if there is a problem finding or invoking the {@code main} method
+     * @throws InvocationTargetException if the {@code main} method throws an exception
+     */
+    private void execute(String mainClassName, String[] appArgs, Context context)
+            throws Fault, InvocationTargetException {
+        ClassLoader cl = context.getClassLoader(ClassLoader.getSystemClassLoader());
+        try {
+            Class<?> appClass = Class.forName(mainClassName, true, cl);
+            if (appClass.getClassLoader() != cl) {
+                throw new Fault(Errors.UnexpectedClass(mainClassName));
+            }
+            Method main = appClass.getDeclaredMethod("main", String[].class);
+            int PUBLIC_STATIC = Modifier.PUBLIC | Modifier.STATIC;
+            if ((main.getModifiers() & PUBLIC_STATIC) != PUBLIC_STATIC) {
+                throw new Fault(Errors.MainNotPublicStatic);
+            }
+            if (!main.getReturnType().equals(void.class)) {
+                throw new Fault(Errors.MainNotVoid);
+            }
+            main.setAccessible(true);
+            main.invoke(0, (Object) appArgs);
+        } catch (ClassNotFoundException e) {
+            throw new Fault(Errors.CantFindClass(mainClassName));
+        } catch (NoSuchMethodException e) {
+            throw new Fault(Errors.CantFindMainMethod(mainClassName));
+        } catch (IllegalAccessException e) {
+            throw new Fault(Errors.CantAccessMainMethod(mainClassName));
+        } catch (InvocationTargetException e) {
+            // remove stack frames for source launcher
+            int invocationFrames = e.getStackTrace().length;
+            Throwable target = e.getTargetException();
+            StackTraceElement[] targetTrace = target.getStackTrace();
+            target.setStackTrace(Arrays.copyOfRange(targetTrace, 0, targetTrace.length - invocationFrames));
+            throw e;
+        }
+    }
+
+    private static final String bundleName = "com.sun.tools.javac.resources.launcher";
+    private ResourceBundle resourceBundle = null;
+    private String errorPrefix;
+
+    /**
+     * Returns a localized string from a resource bundle.
+     *
+     * @param error the error for which to get the localized text
+     * @return the localized string
+     */
+    private String getMessage(Error error) {
+        String key = error.key();
+        Object[] args = error.getArgs();
+        try {
+            if (resourceBundle == null) {
+                resourceBundle = ResourceBundle.getBundle(bundleName);
+                errorPrefix = resourceBundle.getString("launcher.error");
+            }
+            String resource = resourceBundle.getString(key);
+            String message = MessageFormat.format(resource, args);
+            return errorPrefix + message;
+        } catch (MissingResourceException e) {
+            return "Cannot access resource; " + key + Arrays.toString(args);
+        }
+    }
+
+    /**
+     * A listener to detect the first class found in a compilation.
+     */
+    static class MainClassListener implements TaskListener {
+        TypeElement mainClass;
+
+        MainClassListener(JavacTask t) {
+            t.addTaskListener(this);
+        }
+
+        @Override
+        public void started(TaskEvent ev) {
+            if (ev.getKind() == TaskEvent.Kind.ANALYZE && mainClass == null) {
+                TypeElement te = ev.getTypeElement();
+                if (te.getNestingKind() == NestingKind.TOP_LEVEL) {
+                    mainClass = te;
+                }
+            }
+        }
+    }
+
+    /**
+     * An object to encapulate the set of in-memory classes, such that
+     * they can be written by a file manager and subsequently used by
+     * a class loader.
+     */
+    private static class Context {
+        private Map<String, byte[]> inMemoryClasses = new HashMap<>();
+
+        JavaFileManager getFileManager(StandardJavaFileManager delegate) {
+            return new MemoryFileManager(inMemoryClasses, delegate);
+        }
+
+        ClassLoader getClassLoader(ClassLoader parent) {
+            return new MemoryClassLoader(inMemoryClasses, parent);
+        }
+    }
+
+    /**
+     * An in-memory file manager.
+     *
+     * <p>Class files (of kind {@link JavaFileObject.Kind#CLASS CLASS} written to
+     * {@link StandardLocation#CLASS_OUTPUT} will be written to an in-memory cache.
+     * All other file manager operations will be delegated to a specified file manager.
+     */
+    private static class MemoryFileManager extends ForwardingJavaFileManager<JavaFileManager> {
+        private final Map<String, byte[]> map;
+
+        MemoryFileManager(Map<String, byte[]> map, JavaFileManager delegate) {
+            super(delegate);
+            this.map = map;
+        }
+
+        @Override
+        public JavaFileObject getJavaFileForOutput(Location location, String className,
+                JavaFileObject.Kind kind, FileObject sibling) throws IOException {
+            if (location == StandardLocation.CLASS_OUTPUT && kind == JavaFileObject.Kind.CLASS) {
+                return createInMemoryClassFile(className);
+            } else {
+                return super.getJavaFileForOutput(location, className, kind, sibling);
+            }
+        }
+
+        private JavaFileObject createInMemoryClassFile(String className) {
+            URI uri = URI.create("memory:///" + className.replace('.', '/') + ".class");
+            return new SimpleJavaFileObject(uri, JavaFileObject.Kind.CLASS) {
+                @Override
+                public OutputStream openOutputStream() {
+                    return new ByteArrayOutputStream() {
+                        @Override
+                        public void close() throws IOException {
+                            super.close();
+                            map.put(className, toByteArray());
+                        }
+                    };
+                }
+            };
+        }
+    }
+
+    /**
+     * An in-memory classloader, that uses an in-memory cache written by {@link MemoryFileManager}.
+     *
+     * <p>The classloader uses the standard parent-delegation model, just providing
+     * {@code findClass} to find classes in the in-memory cache.
+     */
+    private static class MemoryClassLoader extends ClassLoader {
+        private final Map<String, byte[]> map;
+
+        MemoryClassLoader(Map<String, byte[]> map, ClassLoader parent) {
+            super(parent);
+            this.map = map;
+        }
+
+        @Override
+        protected Class<?> findClass(String name) throws ClassNotFoundException {
+            byte[] bytes = map.get(name);
+            if (bytes == null) {
+                throw new ClassNotFoundException(name);
+            }
+            return defineClass(name, bytes, 0, bytes.length);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher.properties	Thu Jun 07 16:06:49 2018 -0700
@@ -0,0 +1,136 @@
+#
+# Copyright (c) 2018, 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.  Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# 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.
+#
+
+# Messages in this file which use "placeholders" for values (e.g. {0}, {1})
+# are preceded by a stylized comment describing the type of the corresponding
+# values.
+# The simple types currently in use are:
+#
+# annotation        annotation compound
+# boolean           true or false
+# diagnostic        a sub-message; see compiler.misc.*
+# fragment          similar to 'message segment', but with more specific type
+# modifier          a Java modifier; e.g. public, private, protected
+# file              a file URL
+# file object       a file URL - similar to 'file' but typically used for source/class files, hence more specific
+# flag              a Flags.Flag instance
+# name              a name, typically a Java identifier
+# number            an integer
+# option name       the name of a command line option
+# source version    a source version number, such as 1.5, 1.6, 1.7
+# string            a general string
+# symbol            the name of a declared type
+# symbol kind       the kind of a symbol (i.e. method, variable)
+# kind name         an informative description of the kind of a declaration; see compiler.misc.kindname.*
+# token             the name of a non-terminal in source code; see compiler.misc.token.*
+# type              a Java type; e.g. int, X, X<T>
+# object            a Java object (unspecified)
+# unused            the value is not used in this message
+#
+# The following compound types are also used:
+#
+# collection of X   a comma-separated collection of items; e.g. collection of type
+# list of X         a comma-separated list of items; e.g. list of type
+# set of X          a comma-separated set of items; e.g. set of modifier
+#
+# These may be composed:
+#
+# list of type or message segment
+#
+# The following type aliases are supported:
+#
+# message segment --> diagnostic or fragment
+# file name --> file, path or file object
+#
+# Custom comments are supported in parenthesis i.e.
+#
+# number (classfile major version)
+#
+# These comments are used internally in order to generate an enum-like class declaration containing
+# a method/field for each of the diagnostic keys listed here. Those methods/fields can then be used
+# by javac code to build diagnostics in a type-safe fashion.
+#
+# In addition, these comments are verified by the jtreg test test/tools/javac/diags/MessageInfo,
+# using info derived from the collected set of examples in test/tools/javac/diags/examples.
+# MessageInfo can also be run as a standalone utility providing more facilities
+# for manipulating this file. For more details, see MessageInfo.java.
+
+## All errors are preceded by this string.
+launcher.error=\
+    error:\u0020
+
+launcher.err.no.args=\
+    no filename
+
+# 0: string
+launcher.err.invalid.filename=\
+    invalid filename: {0}
+
+# 0: path
+launcher.err.file.not.found=\
+    file not found: {0}
+
+launcher.err.compilation.failed=\
+    compilation failed
+
+launcher.err.no.class=\
+    no class declared in file
+
+launcher.err.main.not.public.static=\
+    ''main'' method is not declared ''public static''
+
+launcher.err.main.not.void=\
+    ''main'' method is not declared with a return type of ''void''
+
+# 0: string
+launcher.err.cant.find.class=\
+    can''t find class: {0}
+
+# 0: string
+launcher.err.unexpected.class=\
+    class found on application class path: {0}
+
+# 0: string
+launcher.err.cant.find.main.method=\
+    can''t find main(String[]) method in class: {0}
+
+# 0: string
+launcher.err.cant.access.main.method=\
+    can''t access main method in class: {0}
+
+# 0: path, 1: object
+launcher.err.cant.read.file=\
+    error reading file {0}: {1}
+
+# 0: string
+launcher.err.no.value.for.option=\
+    no value given for option: {0}
+
+# 0: string
+launcher.err.invalid.value.for.source=\
+    invalid value for --source option: {0}
+
+launcher.err.enable.preview.requires.source=\
+    --enable-preview must be used with --source
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/launcher/SourceMode.java	Thu Jun 07 16:06:49 2018 -0700
@@ -0,0 +1,315 @@
+/*
+ * Copyright (c) 2017, 2018, 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 8192920
+ * @summary Test source mode
+ * @modules jdk.compiler
+ * @run main SourceMode
+ */
+
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFilePermission;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class SourceMode extends TestHelper {
+
+    public static void main(String... args) throws Exception {
+        new SourceMode().run(args);
+    }
+
+    // java Simple.java 1 2 3
+    @Test
+    void testSimpleJava() throws IOException {
+        Path file = getSimpleFile("Simple.java", false);
+        TestResult tr = doExec(javaCmd, file.toString(), "1", "2", "3");
+        if (!tr.isOK())
+            error(tr, "Bad exit code: " + tr.exitValue);
+        if (!tr.contains("[1, 2, 3]"))
+            error(tr, "Expected output not found");
+        System.out.println(tr.testOutput);
+    }
+
+    // java --source 10 simple 1 2 3
+    @Test
+    void testSimple() throws IOException {
+        Path file = getSimpleFile("simple", false);
+        TestResult tr = doExec(javaCmd, "--source", "10", file.toString(), "1", "2", "3");
+        if (!tr.isOK())
+            error(tr, "Bad exit code: " + tr.exitValue);
+        if (!tr.contains("[1, 2, 3]"))
+            error(tr, "Expected output not found");
+        System.out.println(tr.testOutput);
+    }
+
+    // execSimple 1 2 3
+    @Test
+    void testExecSimple() throws IOException {
+        if (isWindows) // Will not work without cygwin, pass silently
+            return;
+        Path file = setExecutable(getSimpleFile("execSimple", true));
+        TestResult tr = doExec(file.toAbsolutePath().toString(), "1", "2", "3");
+        if (!tr.isOK())
+            error(tr, "Bad exit code: " + tr.exitValue);
+        if (!tr.contains("[1, 2, 3]"))
+            error(tr, "Expected output not found");
+        System.out.println(tr.testOutput);
+    }
+
+    // java @simpleJava.at  (contains Simple.java 1 2 3)
+    @Test
+    void testSimpleJavaAtFile() throws IOException {
+        Path file = getSimpleFile("Simple.java", false);
+        Path atFile = Paths.get("simpleJava.at");
+        createFile(atFile.toFile(), List.of(file + " 1 2 3"));
+        TestResult tr = doExec(javaCmd, "@" + atFile);
+        if (!tr.isOK())
+            error(tr, "Bad exit code: " + tr.exitValue);
+        if (!tr.contains("[1, 2, 3]"))
+            error(tr, "Expected output not found");
+        System.out.println(tr.testOutput);
+    }
+
+    // java @simple.at  (contains --source 10 simple 1 2 3)
+    @Test
+    void testSimpleAtFile() throws IOException {
+        Path file = getSimpleFile("simple", false);
+        Path atFile = Paths.get("simple.at");
+        createFile(atFile.toFile(), List.of("--source 10 " + file + " 1 2 3"));
+        TestResult tr = doExec(javaCmd, "@" + atFile);
+        if (!tr.isOK())
+            error(tr, "Bad exit code: " + tr.exitValue);
+        if (!tr.contains("[1, 2, 3]"))
+            error(tr, "Expected output not found");
+        System.out.println(tr.testOutput);
+    }
+
+    // java -cp classes Main.java 1 2 3
+    @Test
+    void testClasspath() throws IOException {
+        Path base = Files.createDirectories(Paths.get("testClasspath"));
+        Path otherJava = base.resolve("Other.java");
+        createFile(otherJava.toFile(), List.of(
+            "public class Other {",
+            "  public static String join(String[] args) {",
+            "    return String.join(\"-\", args);",
+            "  }",
+            "}"
+        ));
+        Path classes = Files.createDirectories(base.resolve("classes"));
+        Path mainJava = base.resolve("Main.java");
+        createFile(mainJava.toFile(), List.of(
+            "class Main {",
+            "  public static void main(String[] args) {",
+            "    System.out.println(Other.join(args));",
+            "  }}"
+        ));
+        compile("-d", classes.toString(), otherJava.toString());
+        TestResult tr = doExec(javaCmd, "-cp", classes.toString(),
+                mainJava.toString(), "1", "2", "3");
+        if (!tr.isOK())
+            error(tr, "Bad exit code: " + tr.exitValue);
+        if (!tr.contains("1-2-3"))
+            error(tr, "Expected output not found");
+        System.out.println(tr.testOutput);
+    }
+
+    // java --add-exports=... Export.java --help
+    @Test
+    void testAddExports() throws IOException {
+        Path exportJava = Paths.get("Export.java");
+        createFile(exportJava.toFile(), List.of(
+            "public class Export {",
+            "  public static void main(String[] args) {",
+            "    new com.sun.tools.javac.main.Main(\"demo\").compile(args);",
+            "  }",
+            "}"
+        ));
+        // verify access fails without --add-exports
+        TestResult tr1 = doExec(javaCmd, exportJava.toString(), "--help");
+        if (tr1.isOK())
+            error(tr1, "Compilation succeeded unexpectedly");
+        // verify access succeeds with --add-exports
+        TestResult tr2 = doExec(javaCmd,
+            "--add-exports", "jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
+            exportJava.toString(), "--help");
+        if (!tr2.isOK())
+            error(tr2, "Bad exit code: " + tr2.exitValue);
+        if (!(tr2.contains("demo") && tr2.contains("Usage")))
+            error(tr2, "Expected output not found");
+    }
+
+    // java -cp ... HelloWorld.java  (for a class "java" in package "HelloWorld")
+    @Test
+    void testClassNamedJava() throws IOException {
+        Path base = Files.createDirectories(Paths.get("testClassNamedJava"));
+        Path src = Files.createDirectories(base.resolve("src"));
+        Path srcfile = src.resolve("java.java");
+        createFile(srcfile.toFile(), List.of(
+                "package HelloWorld;",
+                "class java {",
+                "    public static void main(String... args) {",
+                "        System.out.println(HelloWorld.java.class.getName());",
+                "    }",
+                "}"
+        ));
+        Path classes = base.resolve("classes");
+        compile("-d", classes.toString(), srcfile.toString());
+        TestResult tr =
+            doExec(javaCmd, "-cp", classes.toString(), "HelloWorld.java");
+        if (!tr.isOK())
+            error(tr, "Command failed");
+        if (!tr.contains("HelloWorld.java"))
+            error(tr, "Expected output not found");
+    }
+
+    // java --source
+    @Test
+    void testSourceNoArg() throws IOException {
+        TestResult tr = doExec(javaCmd, "--source");
+        if (tr.isOK())
+            error(tr, "Command succeeded unexpectedly");
+        System.err.println(tr);
+        if (!tr.contains("--source requires source version"))
+            error(tr, "Expected output not found");
+    }
+
+    // java --source 10 -jar simple.jar
+    @Test
+    void testSourceJarConflict() throws IOException {
+        Path base = Files.createDirectories(Paths.get("testSourceJarConflict"));
+        Path file = getSimpleFile("Simple.java", false);
+        Path classes = Files.createDirectories(base.resolve("classes"));
+        compile("-d", classes.toString(), file.toString());
+        Path simpleJar = base.resolve("simple.jar");
+        createJar("cf", simpleJar.toString(), "-C", classes.toString(), ".");
+        TestResult tr =
+            doExec(javaCmd, "--source", "10", "-jar", simpleJar.toString());
+        if (tr.isOK())
+            error(tr, "Command succeeded unexpectedly");
+        if (!tr.contains("Option -jar is not allowed with --source"))
+            error(tr, "Expected output not found");
+    }
+
+    // java --source 10 -m jdk.compiler
+    @Test
+    void testSourceModuleConflict() throws IOException {
+        TestResult tr = doExec(javaCmd, "--source", "10", "-m", "jdk.compiler");
+        if (tr.isOK())
+            error(tr, "Command succeeded unexpectedly");
+        if (!tr.contains("Option -m is not allowed with --source"))
+            error(tr, "Expected output not found");
+    }
+
+    // #!.../java --source 10 -version
+    @Test
+    void testTerminalOptionInShebang() throws IOException {
+        if (isWindows) // Will not work without cygwin, pass silently
+            return;
+        Path base = Files.createDirectories(
+            Paths.get("testTerminalOptionInShebang"));
+        Path bad = base.resolve("bad");
+        createFile(bad.toFile(), List.of(
+            "#!" + javaCmd + " --source 10 -version"));
+        setExecutable(bad);
+        TestResult tr = doExec(bad.toString());
+        if (!tr.contains("Option -version is not allowed in this context"))
+            error(tr, "Expected output not found");
+    }
+
+    // #!.../java --source 10 @bad.at  (contains -version)
+    @Test
+    void testTerminalOptionInShebangAtFile() throws IOException {
+        if (isWindows) // Will not work without cygwin, pass silently
+            return;
+        // Use a short directory name, to avoid line length limitations
+        Path base = Files.createDirectories(Paths.get("testBadAtFile"));
+        Path bad_at = base.resolve("bad.at");
+        createFile(bad_at.toFile(), List.of("-version"));
+        Path bad = base.resolve("bad");
+        createFile(bad.toFile(), List.of(
+            "#!" + javaCmd + " --source 10 @" + bad_at));
+        setExecutable(bad);
+        TestResult tr = doExec(bad.toString());
+        System.err.println("JJG JJG " + tr);
+        if (!tr.contains("Option -version in @testBadAtFile/bad.at is "
+                + "not allowed in this context"))
+            error(tr, "Expected output not found");
+    }
+
+    // #!.../java --source 10 HelloWorld
+    @Test
+    void testMainClassInShebang() throws IOException {
+        if (isWindows) // Will not work without cygwin, pass silently
+            return;
+        Path base = Files.createDirectories(Paths.get("testMainClassInShebang"));
+        Path bad = base.resolve("bad");
+        createFile(bad.toFile(), List.of(
+            "#!" + javaCmd + " --source 10 HelloWorld"));
+        setExecutable(bad);
+        TestResult tr = doExec(bad.toString());
+        if (!tr.contains("Cannot specify main class in this context"))
+            error(tr, "Expected output not found");
+    }
+
+    //--------------------------------------------------------------------------
+
+    private Map<String,String> getLauncherDebugEnv() {
+        return Map.of("_JAVA_LAUNCHER_DEBUG", "1");
+    }
+
+    private Path getSimpleFile(String name, boolean shebang) throws IOException {
+        Path file = Paths.get(name);
+        if (!Files.exists(file)) {
+            createFile(file.toFile(), List.of(
+                (shebang ? "#!" + javaCmd + " --source 10" : ""),
+                "public class Simple {",
+                "  public static void main(String[] args) {",
+                "    System.out.println(java.util.Arrays.toString(args));",
+                "  }}"));
+        }
+        return file;
+    }
+
+    private Path setExecutable(Path file) throws IOException {
+        Set<PosixFilePermission> perms = Files.getPosixFilePermissions(file);
+        perms.add(PosixFilePermission.OWNER_EXECUTE);
+        Files.setPosixFilePermissions(file, perms);
+        return file;
+    }
+
+    private void error(TestResult tr, String message) {
+        System.err.println(tr);
+        throw new RuntimeException(message);
+    }
+}
--- a/test/langtools/tools/javac/diags/CheckResourceKeys.java	Thu Jun 07 23:30:05 2018 +0200
+++ b/test/langtools/tools/javac/diags/CheckResourceKeys.java	Thu Jun 07 16:06:49 2018 -0700
@@ -118,7 +118,8 @@
     void findDeadKeys(Set<String> codeStrings, Set<String> resourceKeys) {
         String[] prefixes = {
             "compiler.err.", "compiler.warn.", "compiler.note.", "compiler.misc.",
-            "javac."
+            "javac.",
+            "launcher.err."
         };
         for (String rk: resourceKeys) {
             // some keys are used directly, without a prefix.
@@ -395,7 +396,7 @@
     Set<String> getResourceKeys() {
         Module jdk_compiler = ModuleLayer.boot().findModule("jdk.compiler").get();
         Set<String> results = new TreeSet<String>();
-        for (String name : new String[]{"javac", "compiler"}) {
+        for (String name : new String[]{"javac", "compiler", "launcher"}) {
             ResourceBundle b =
                     ResourceBundle.getBundle("com.sun.tools.javac.resources." + name, jdk_compiler);
             results.addAll(b.keySet());
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/langtools/tools/javac/launcher/SourceLauncherTest.java	Thu Jun 07 16:06:49 2018 -0700
@@ -0,0 +1,542 @@
+/*
+ * Copyright (c) 2018, 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 8192920
+ * @summary Test source launcher
+ * @library /tools/lib
+ * @modules jdk.compiler/com.sun.tools.javac.api
+ *          jdk.compiler/com.sun.tools.javac.launcher
+ *          jdk.compiler/com.sun.tools.javac.main
+ * @build toolbox.JavaTask toolbox.JavacTask toolbox.TestRunner toolbox.ToolBox
+ * @run main SourceLauncherTest
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+
+import com.sun.tools.javac.launcher.Main;
+
+import toolbox.JavaTask;
+import toolbox.JavacTask;
+import toolbox.Task;
+import toolbox.TestRunner;
+import toolbox.TestRunner;
+import toolbox.ToolBox;
+
+public class SourceLauncherTest extends TestRunner {
+    public static void main(String... args) throws Exception {
+        SourceLauncherTest t = new SourceLauncherTest();
+        t.runTests(m -> new Object[] { Paths.get(m.getName()) });
+    }
+
+    SourceLauncherTest() {
+        super(System.err);
+        tb = new ToolBox();
+        System.err.println("version: " + thisVersion);
+    }
+
+    private final ToolBox tb;
+    private static final String thisVersion = System.getProperty("java.specification.version");
+
+    /*
+     * Positive tests.
+     */
+
+    @Test
+    public void testHelloWorld(Path base) throws IOException {
+        tb.writeJavaFiles(base,
+            "import java.util.Arrays;\n" +
+            "class HelloWorld {\n" +
+            "    public static void main(String... args) {\n" +
+            "        System.out.println(\"Hello World! \" + Arrays.toString(args));\n" +
+            "    }\n" +
+            "}");
+        testSuccess(base.resolve("HelloWorld.java"), "Hello World! [1, 2, 3]\n");
+    }
+
+    @Test
+    public void testHelloWorldInPackage(Path base) throws IOException {
+        tb.writeJavaFiles(base,
+            "package hello;\n" +
+            "import java.util.Arrays;\n" +
+            "class World {\n" +
+            "    public static void main(String... args) {\n" +
+            "        System.out.println(\"Hello World! \" + Arrays.toString(args));\n" +
+            "    }\n" +
+            "}");
+        testSuccess(base.resolve("hello").resolve("World.java"), "Hello World! [1, 2, 3]\n");
+    }
+
+    @Test
+    public void testHelloWorldWithAux(Path base) throws IOException {
+        tb.writeJavaFiles(base,
+            "import java.util.Arrays;\n" +
+            "class HelloWorld {\n" +
+            "    public static void main(String... args) {\n" +
+            "        Aux.write(args);\n" +
+            "    }\n" +
+            "}\n" +
+            "class Aux {\n" +
+            "    static void write(String... args) {\n" +
+            "        System.out.println(\"Hello World! \" + Arrays.toString(args));\n" +
+            "    }\n" +
+            "}");
+        testSuccess(base.resolve("HelloWorld.java"), "Hello World! [1, 2, 3]\n");
+    }
+
+    @Test
+    public void testHelloWorldWithShebang(Path base) throws IOException {
+        tb.writeJavaFiles(base,
+            "#!/usr/bin/java --source 11\n" +
+            "import java.util.Arrays;\n" +
+            "class HelloWorld {\n" +
+            "    public static void main(String... args) {\n" +
+            "        System.out.println(\"Hello World! \" + Arrays.toString(args));\n" +
+            "    }\n" +
+            "}");
+        Files.copy(base.resolve("HelloWorld.java"), base.resolve("HelloWorld"));
+        testSuccess(base.resolve("HelloWorld"), "Hello World! [1, 2, 3]\n");
+    }
+
+    @Test
+    public void testNoAnnoProcessing(Path base) throws IOException {
+        Path annoSrc = base.resolve("annoSrc");
+        tb.writeJavaFiles(annoSrc,
+            "import java.util.*;\n" +
+            "import javax.annotation.processing.*;\n" +
+            "import javax.lang.model.element.*;\n" +
+            "@SupportedAnnotationTypes(\"*\")\n" +
+            "public class AnnoProc extends AbstractProcessor {\n" +
+            "    public boolean process(Set<? extends TypeElement> annos, RoundEnvironment rEnv) {\n" +
+            "        throw new Error(\"Annotation processor should not be invoked\");\n" +
+            "    }\n" +
+            "}\n");
+        Path annoClasses = Files.createDirectories(base.resolve("classes"));
+        new JavacTask(tb)
+                .outdir(annoClasses)
+                .files(annoSrc.resolve("AnnoProc.java").toString())
+                .run();
+        Path serviceFile = annoClasses.resolve("META-INF").resolve("services")
+                .resolve("javax.annotation.processing.Processor");
+        tb.writeFile(serviceFile, "AnnoProc");
+
+        Path mainSrc = base.resolve("mainSrc");
+        tb.writeJavaFiles(mainSrc,
+            "import java.util.Arrays;\n" +
+            "class HelloWorld {\n" +
+            "    public static void main(String... args) {\n" +
+            "        System.out.println(\"Hello World! \" + Arrays.toString(args));\n" +
+            "    }\n" +
+            "}");
+
+        List<String> javacArgs = List.of("-classpath", annoClasses.toString());
+        List<String> classArgs = List.of("1", "2", "3");
+        String expect = "Hello World! [1, 2, 3]\n";
+        Result r = run(mainSrc.resolve("HelloWorld.java"), javacArgs, classArgs);
+        checkEqual("stdout", r.stdOut, expect);
+        checkEmpty("stderr", r.stdErr);
+        checkNull("exception", r.exception);
+    }
+
+    @Test
+    public void testEnablePreview(Path base) throws IOException {
+        tb.writeJavaFiles(base,
+            "import java.util.Arrays;\n" +
+            "class HelloWorld {\n" +
+            "    public static void main(String... args) {\n" +
+            "        System.out.println(\"Hello World! \" + Arrays.toString(args));\n" +
+            "    }\n" +
+            "}");
+
+        String log = new JavaTask(tb)
+                .vmOptions("--enable-preview", "--source", thisVersion)
+                .className(base.resolve("HelloWorld.java").toString())
+                .classArgs("1", "2", "3")
+                .run(Task.Expect.SUCCESS)
+                .getOutput(Task.OutputKind.STDOUT);
+        checkEqual("stdout", log.trim(), "Hello World! [1, 2, 3]");
+    }
+
+    void testSuccess(Path file, String expect) throws IOException {
+        Result r = run(file, Collections.emptyList(), List.of("1", "2", "3"));
+        checkEqual("stdout", r.stdOut, expect);
+        checkEmpty("stderr", r.stdErr);
+        checkNull("exception", r.exception);
+    }
+
+    /*
+     * Negative tests: such as cannot find or execute main method.
+     */
+
+    @Test
+    public void testHelloWorldWithShebangJava(Path base) throws IOException {
+        tb.writeJavaFiles(base,
+            "#!/usr/bin/java --source 11\n" +
+            "import java.util.Arrays;\n" +
+            "class HelloWorld {\n" +
+            "    public static void main(String... args) {\n" +
+            "        System.out.println(\"Hello World! \" + Arrays.toString(args));\n" +
+            "    }\n" +
+            "}");
+        Path file = base.resolve("HelloWorld.java");
+        testError(file,
+            file + ":1: error: illegal character: '#'\n" +
+            "#!/usr/bin/java --source 11\n" +
+            "^\n" +
+            file + ":1: error: class, interface, or enum expected\n" +
+            "#!/usr/bin/java --source 11\n" +
+            "  ^\n" +
+            "2 errors\n",
+            "error: compilation failed");
+    }
+
+    @Test
+    public void testNoClass(Path base) throws IOException {
+        Files.createDirectories(base);
+        Path file = base.resolve("NoClass.java");
+        Files.write(file, List.of("package p;"));
+        testError(file, "", "error: no class declared in file");
+    }
+
+    @Test
+    public void testWrongClass(Path base) throws IOException {
+        Path src = base.resolve("src");
+        Path file = src.resolve("WrongClass.java");
+        tb.writeJavaFiles(src, "class WrongClass { }");
+        Path classes = Files.createDirectories(base.resolve("classes"));
+        new JavacTask(tb)
+                .outdir(classes)
+                .files(file)
+                .run();
+        String log = new JavaTask(tb)
+                .classpath(classes.toString())
+                .className(file.toString())
+                .run(Task.Expect.FAIL)
+                .getOutput(Task.OutputKind.STDERR);
+        checkEqual("stderr", log.trim(),
+                "error: class found on application class path: WrongClass");
+    }
+
+    @Test
+    public void testSyntaxErr(Path base) throws IOException {
+        tb.writeJavaFiles(base, "class SyntaxErr {");
+        Path file = base.resolve("SyntaxErr.java");
+        testError(file,
+                file + ":1: error: reached end of file while parsing\n" +
+                "class SyntaxErr {\n" +
+                "                 ^\n" +
+                "1 error\n",
+                "error: compilation failed");
+    }
+
+    @Test
+    public void testNoSourceOnClassPath(Path base) throws IOException {
+        Path auxSrc = base.resolve("auxSrc");
+        tb.writeJavaFiles(auxSrc,
+            "public class Aux {\n" +
+            "    static final String MESSAGE = \"Hello World\";\n" +
+            "}\n");
+
+        Path mainSrc = base.resolve("mainSrc");
+        tb.writeJavaFiles(mainSrc,
+            "import java.util.Arrays;\n" +
+            "class HelloWorld {\n" +
+            "    public static void main(String... args) {\n" +
+            "        System.out.println(Aux.MESSAGE + Arrays.toString(args));\n" +
+            "    }\n" +
+            "}");
+
+        List<String> javacArgs = List.of("-classpath", auxSrc.toString());
+        List<String> classArgs = List.of("1", "2", "3");
+        String expectStdErr =
+            "testNoSourceOnClassPath/mainSrc/HelloWorld.java:4: error: cannot find symbol\n" +
+            "        System.out.println(Aux.MESSAGE + Arrays.toString(args));\n" +
+            "                           ^\n" +
+            "  symbol:   variable Aux\n" +
+            "  location: class HelloWorld\n" +
+            "1 error\n";
+        Result r = run(mainSrc.resolve("HelloWorld.java"), javacArgs, classArgs);
+        checkEmpty("stdout", r.stdOut);
+        checkEqual("stderr", r.stdErr, expectStdErr);
+        checkFault("exception", r.exception, "error: compilation failed");
+
+    }
+
+    // For any source file that is invoked through the OS shebang mechanism, invalid shebang
+    // lines will be caught and handled by the OS, before the launcher is even invoked.
+    // However, if such a file is passed directly to the launcher, perhaps using the --source
+    // option, a well-formed shebang line will be removed but a badly-formed one will be not be
+    // removed and will cause compilation errors.
+    @Test
+    public void testBadShebang(Path base) throws IOException {
+        tb.writeJavaFiles(base,
+            "#/usr/bin/java --source 11\n" +
+            "import java.util.Arrays;\n" +
+            "class HelloWorld {\n" +
+            "    public static void main(String... args) {\n" +
+            "        System.out.println(\"Hello World! \" + Arrays.toString(args));\n" +
+            "    }\n" +
+            "}");
+        Path file = base.resolve("HelloWorld.java");
+        testError(file,
+            file + ":1: error: illegal character: '#'\n" +
+            "#/usr/bin/java --source 11\n" +
+            "^\n" +
+            file + ":1: error: class, interface, or enum expected\n" +
+            "#/usr/bin/java --source 11\n" +
+            "  ^\n" +
+            "2 errors\n",
+            "error: compilation failed");
+    }
+
+    @Test
+    public void testBadSourceOpt(Path base) throws IOException {
+        Files.createDirectories(base);
+        Path file = base.resolve("DummyClass.java");
+        Files.write(file, List.of("class DummyClass { }"));
+        Properties sysProps = System.getProperties();
+        Properties p = new Properties(sysProps);
+        p.setProperty("jdk.internal.javac.source", "<BAD>");
+        System.setProperties(p);
+        try {
+            testError(file, "", "error: invalid value for --source option: <BAD>");
+        } finally {
+            System.setProperties(sysProps);
+        }
+    }
+
+    @Test
+    public void testEnablePreviewNoSource(Path base) throws IOException {
+        tb.writeJavaFiles(base,
+            "import java.util.Arrays;\n" +
+            "class HelloWorld {\n" +
+            "    public static void main(String... args) {\n" +
+            "        System.out.println(\"Hello World! \" + Arrays.toString(args));\n" +
+            "    }\n" +
+            "}");
+
+        String log = new JavaTask(tb)
+                .vmOptions("--enable-preview")
+                .className(base.resolve("HelloWorld.java").toString())
+                .run(Task.Expect.FAIL)
+                .getOutput(Task.OutputKind.STDERR);
+        checkEqual("stderr", log.trim(),
+                "error: --enable-preview must be used with --source");
+    }
+
+    @Test
+    public void testNoMain(Path base) throws IOException {
+        tb.writeJavaFiles(base, "class NoMain { }");
+        testError(base.resolve("NoMain.java"), "",
+                "error: can't find main(String[]) method in class: NoMain");
+    }
+
+    @Test
+    public void testMainBadParams(Path base) throws IOException {
+        tb.writeJavaFiles(base,
+                "class BadParams { public static void main() { } }");
+        testError(base.resolve("BadParams.java"), "",
+                "error: can't find main(String[]) method in class: BadParams");
+    }
+
+    @Test
+    public void testMainNotPublic(Path base) throws IOException {
+        tb.writeJavaFiles(base,
+                "class NotPublic { static void main(String... args) { } }");
+        testError(base.resolve("NotPublic.java"), "",
+                "error: 'main' method is not declared 'public static'");
+    }
+
+    @Test
+    public void testMainNotStatic(Path base) throws IOException {
+        tb.writeJavaFiles(base,
+                "class NotStatic { public void main(String... args) { } }");
+        testError(base.resolve("NotStatic.java"), "",
+                "error: 'main' method is not declared 'public static'");
+    }
+
+    @Test
+    public void testMainNotVoid(Path base) throws IOException {
+        tb.writeJavaFiles(base,
+                "class NotVoid { public static int main(String... args) { return 0; } }");
+        testError(base.resolve("NotVoid.java"), "",
+                "error: 'main' method is not declared with a return type of 'void'");
+    }
+
+    @Test
+    public void testClassInModule(Path base) throws IOException {
+        tb.writeJavaFiles(base, "package java.net; class InModule { }");
+        Path file = base.resolve("java").resolve("net").resolve("InModule.java");
+        testError(file,
+                file + ":1: error: package exists in another module: java.base\n" +
+                "package java.net; class InModule { }\n" +
+                "^\n" +
+                "1 error\n",
+                "error: compilation failed");
+    }
+
+    void testError(Path file, String expectStdErr, String expectFault) throws IOException {
+        Result r = run(file, Collections.emptyList(), List.of("1", "2", "3"));
+        checkEmpty("stdout", r.stdOut);
+        checkEqual("stderr", r.stdErr, expectStdErr);
+        checkFault("exception", r.exception, expectFault);
+    }
+
+    /*
+     * Tests in which main throws an exception.
+     */
+    @Test
+    public void testTargetException1(Path base) throws IOException {
+        tb.writeJavaFiles(base,
+            "import java.util.Arrays;\n" +
+            "class Thrower {\n" +
+            "    public static void main(String... args) {\n" +
+            "        throwWhenZero(Integer.parseInt(args[0]));\n" +
+            "    }\n" +
+            "    static void throwWhenZero(int arg) {\n" +
+            "        if (arg == 0) throw new Error(\"zero!\");\n" +
+            "        throwWhenZero(arg - 1);\n" +
+            "    }\n" +
+            "}");
+        Path file = base.resolve("Thrower.java");
+        Result r = run(file, Collections.emptyList(), List.of("3"));
+        checkEmpty("stdout", r.stdOut);
+        checkEmpty("stderr", r.stdErr);
+        checkTrace("exception", r.exception,
+                "java.lang.Error: zero!",
+                "at Thrower.throwWhenZero(Thrower.java:7)",
+                "at Thrower.throwWhenZero(Thrower.java:8)",
+                "at Thrower.throwWhenZero(Thrower.java:8)",
+                "at Thrower.throwWhenZero(Thrower.java:8)",
+                "at Thrower.main(Thrower.java:4)");
+    }
+
+    Result run(Path file, List<String> runtimeArgs, List<String> appArgs) {
+        List<String> args = new ArrayList<>();
+        args.add(file.toString());
+        args.addAll(appArgs);
+
+        PrintStream prev = System.out;
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (PrintStream out = new PrintStream(baos, true)) {
+            System.setOut(out);
+            StringWriter sw = new StringWriter();
+            try (PrintWriter err = new PrintWriter(sw, true)) {
+                Main m = new Main(err);
+                m.run(toArray(runtimeArgs), toArray(args));
+                return new Result(baos.toString(), sw.toString(), null);
+            } catch (Throwable t) {
+                return new Result(baos.toString(), sw.toString(), t);
+            }
+        } finally {
+            System.setOut(prev);
+        }
+    }
+
+    void checkEqual(String name, String found, String expect) {
+        expect = expect.replace("\n", tb.lineSeparator);
+        out.println(name + ": " + found);
+        out.println(name + ": " + found);
+        if (!expect.equals(found)) {
+            error("Unexpected output; expected: " + expect);
+        }
+    }
+
+    void checkEmpty(String name, String found) {
+        out.println(name + ": " + found);
+        if (!found.isEmpty()) {
+            error("Unexpected output; expected empty string");
+        }
+    }
+
+    void checkNull(String name, Throwable found) {
+        out.println(name + ": " + found);
+        if (found != null) {
+            error("Unexpected exception; expected null");
+        }
+    }
+
+    void checkFault(String name, Throwable found, String expect) {
+        expect = expect.replace("\n", tb.lineSeparator);
+        out.println(name + ": " + found);
+        if (found == null) {
+            error("No exception thrown; expected Main.Fault");
+        } else {
+            if (!(found instanceof Main.Fault)) {
+                error("Unexpected exception; expected Main.Fault");
+            }
+            if (!(found.getMessage().equals(expect))) {
+                error("Unexpected detail message; expected: " + expect);
+            }
+        }
+    }
+
+    void checkTrace(String name, Throwable found, String... expect) {
+        if (!(found instanceof InvocationTargetException)) {
+            error("Unexpected exception; expected InvocationTargetException");
+            out.println("Found:");
+            found.printStackTrace(out);
+        }
+        StringWriter sw = new StringWriter();
+        try (PrintWriter pw = new PrintWriter(sw)) {
+            ((InvocationTargetException) found).getTargetException().printStackTrace(pw);
+        }
+        String trace = sw.toString();
+        out.println(name + ":\n" + trace);
+        String[] traceLines = trace.trim().split("[\r\n]+\\s+");
+        try {
+            tb.checkEqual(List.of(traceLines), List.of(expect));
+        } catch (Error e) {
+            error(e.getMessage());
+        }
+    }
+
+    String[] toArray(List<String> list) {
+        return list.toArray(new String[list.size()]);
+    }
+
+    class Result {
+        private final String stdOut;
+        private final String stdErr;
+        private final Throwable exception;
+
+        Result(String stdOut, String stdErr, Throwable exception) {
+            this.stdOut = stdOut;
+            this.stdErr = stdErr;
+            this.exception = exception;
+        }
+    }
+}