8306914: Implement JEP 458: Launch Multi-File Source-Code Programs
Reviewed-by: jlahoda, jlaskey
This commit is contained in:
parent
aec386596d
commit
517b178819
@ -99,6 +99,9 @@ define SetupInterimModule
|
||||
EXCLUDE_FILES := $(TOPDIR)/src/$1/share/classes/module-info.java \
|
||||
$(TOPDIR)/src/$1/share/classes/javax/tools/ToolProvider.java \
|
||||
$(TOPDIR)/src/$1/share/classes/com/sun/tools/javac/launcher/Main.java \
|
||||
$(TOPDIR)/src/$1/share/classes/com/sun/tools/javac/launcher/MemoryContext.java \
|
||||
$(TOPDIR)/src/$1/share/classes/com/sun/tools/javac/launcher/MemoryModuleFinder.java \
|
||||
$(TOPDIR)/src/$1/share/classes/com/sun/tools/javac/launcher/SourceLauncher.java \
|
||||
Standard.java, \
|
||||
EXTRA_FILES := $(BUILDTOOLS_OUTPUTDIR)/gensrc/$1.interim/module-info.java \
|
||||
$($1.interim_EXTRA_FILES), \
|
||||
|
@ -228,6 +228,7 @@ module java.base {
|
||||
java.instrument,
|
||||
java.management.rmi,
|
||||
jdk.jartool,
|
||||
jdk.compiler,
|
||||
jdk.jfr,
|
||||
jdk.jlink,
|
||||
jdk.jpackage;
|
||||
|
@ -32,7 +32,7 @@ java.launcher.opt.header = Usage: {0} [options] <mainclass> [args...]\n\
|
||||
\ {0} [options] --module <module>[/<mainclass>] [args...]\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\
|
||||
\ (to execute a 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\
|
||||
|
@ -178,7 +178,7 @@ static void FreeKnownVMs();
|
||||
static jboolean IsWildCardEnabled();
|
||||
|
||||
|
||||
#define SOURCE_LAUNCHER_MAIN_ENTRY "jdk.compiler/com.sun.tools.javac.launcher.Main"
|
||||
#define SOURCE_LAUNCHER_MAIN_ENTRY "jdk.compiler/com.sun.tools.javac.launcher.SourceLauncher"
|
||||
|
||||
/*
|
||||
* This reports error. VM will not be created and no usage is printed.
|
||||
|
@ -47,9 +47,6 @@ import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.sun.tools.javac.code.Flags.RECORD;
|
||||
import static com.sun.tools.javac.code.Flags.SEALED;
|
||||
import static com.sun.tools.javac.code.Flags.NON_SEALED;
|
||||
import static com.sun.tools.javac.main.Option.PREVIEW;
|
||||
import com.sun.tools.javac.util.JCDiagnostic;
|
||||
|
||||
@ -84,7 +81,7 @@ public class Preview {
|
||||
private final Log log;
|
||||
private final Source source;
|
||||
|
||||
private static final Context.Key<Preview> previewKey = new Context.Key<>();
|
||||
protected static final Context.Key<Preview> previewKey = new Context.Key<>();
|
||||
|
||||
public static Preview instance(Context context) {
|
||||
Preview instance = context.get(previewKey);
|
||||
@ -94,7 +91,8 @@ public class Preview {
|
||||
return instance;
|
||||
}
|
||||
|
||||
Preview(Context context) {
|
||||
@SuppressWarnings("this-escape")
|
||||
protected Preview(Context context) {
|
||||
context.put(previewKey, this);
|
||||
Options options = Options.instance(context);
|
||||
names = Names.instance(context);
|
||||
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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 com.sun.tools.javac.util.JCDiagnostic.Error;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
/**
|
||||
* A runtime exception used to report errors.
|
||||
*
|
||||
* <p><strong>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.</strong></p>
|
||||
*/
|
||||
public final class Fault extends RuntimeException {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 2L;
|
||||
|
||||
private static final String BUNDLE_NAME = "com.sun.tools.javac.resources.launcher";
|
||||
|
||||
private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME);
|
||||
|
||||
private static final String ERROR_PREFIX = RESOURCE_BUNDLE.getString("launcher.error");
|
||||
|
||||
/**
|
||||
* Returns a localized string from a resource bundle.
|
||||
*
|
||||
* @param error the error for which to get the localized text
|
||||
* @return the localized string
|
||||
*/
|
||||
private static String getMessage(Error error) {
|
||||
String key = error.key();
|
||||
Object[] args = error.getArgs();
|
||||
try {
|
||||
String resource = RESOURCE_BUNDLE.getString(key);
|
||||
String message = MessageFormat.format(resource, args);
|
||||
return ERROR_PREFIX + message;
|
||||
} catch (MissingResourceException e) {
|
||||
return "Cannot access resource; " + key + Arrays.toString(args);
|
||||
}
|
||||
}
|
||||
|
||||
Fault(Error error) {
|
||||
super(getMessage(error));
|
||||
}
|
||||
}
|
@ -1,827 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2023, 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.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
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.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
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.security.CodeSigner;
|
||||
import java.security.CodeSource;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.NoSuchElementException;
|
||||
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.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.MethodFinder;
|
||||
import jdk.internal.misc.PreviewFeatures;
|
||||
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 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)
|
||||
.checkSecurityManager()
|
||||
.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.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
/** 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a security manager is present and throws an exception if so.
|
||||
* @return this object
|
||||
* @throws Fault if a security manager is present
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
private Main checkSecurityManager() throws Fault {
|
||||
if (System.getSecurityManager() != null) {
|
||||
throw new Fault(Errors.SecurityManager);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(file.toAbsolutePath());
|
||||
String mainClassName = compile(file, getJavacOpts(runtimeArgs), context);
|
||||
|
||||
String[] mainArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
execute(mainClassName, mainArgs, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path for the filename found in the first of an array of arguments.
|
||||
*
|
||||
* @param args the array
|
||||
* @return the path, as given in the array of args
|
||||
* @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:
|
||||
if (opt.startsWith("-agentlib:jdwp=") || opt.startsWith("-Xrunjdwp:")) {
|
||||
javacOpts.add("-g");
|
||||
}
|
||||
// ignore all other runtime args
|
||||
}
|
||||
}
|
||||
|
||||
// add implicit options
|
||||
javacOpts.add("-proc:none");
|
||||
javacOpts.add("-Xdiags:verbose");
|
||||
javacOpts.add("-Xlint:deprecation");
|
||||
javacOpts.add("-Xlint:unchecked");
|
||||
javacOpts.add("-Xlint:-options");
|
||||
javacOpts.add("-XDsourceLauncher");
|
||||
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);
|
||||
}
|
||||
TypeElement mainClass = l.mainClass;
|
||||
String mainClassName = 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 mainArgs 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[] mainArgs, Context context)
|
||||
throws Fault, InvocationTargetException {
|
||||
System.setProperty("jdk.launcher.sourcefile", context.file.toString());
|
||||
ClassLoader cl = context.getClassLoader(ClassLoader.getSystemClassLoader());
|
||||
|
||||
Class<?> appClass;
|
||||
try {
|
||||
appClass = Class.forName(mainClassName, true, cl);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new Fault(Errors.CantFindClass(mainClassName));
|
||||
}
|
||||
|
||||
Method mainMethod = MethodFinder.findMainMethod(appClass);
|
||||
|
||||
if (mainMethod == null) {
|
||||
throw new Fault(Errors.CantFindMainMethod(mainClassName));
|
||||
}
|
||||
|
||||
boolean isStatic = Modifier.isStatic(mainMethod.getModifiers());
|
||||
Object instance = null;
|
||||
|
||||
if (!isStatic) {
|
||||
Constructor<?> constructor;
|
||||
try {
|
||||
constructor = appClass.getDeclaredConstructor();
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new Fault(Errors.CantFindConstructor(mainClassName));
|
||||
}
|
||||
|
||||
try {
|
||||
constructor.setAccessible(true);
|
||||
instance = constructor.newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new Fault(Errors.CantAccessConstructor(mainClassName));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Similar to sun.launcher.LauncherHelper#executeMainClass
|
||||
// but duplicated here to prevent additional launcher frames
|
||||
mainMethod.setAccessible(true);
|
||||
Object receiver = isStatic ? appClass : instance;
|
||||
|
||||
if (mainMethod.getParameterCount() == 0) {
|
||||
mainMethod.invoke(receiver);
|
||||
} else {
|
||||
mainMethod.invoke(receiver, (Object)mainArgs);
|
||||
}
|
||||
} 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.getCause();
|
||||
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 encapsulate 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 final Path file;
|
||||
private final Map<String, byte[]> inMemoryClasses = new HashMap<>();
|
||||
|
||||
Context(Path file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
JavaFileManager getFileManager(StandardJavaFileManager delegate) {
|
||||
return new MemoryFileManager(inMemoryClasses, delegate);
|
||||
}
|
||||
|
||||
ClassLoader getClassLoader(ClassLoader parent) {
|
||||
return new MemoryClassLoader(inMemoryClasses, parent, file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 of classes written by
|
||||
* {@link MemoryFileManager}.
|
||||
*
|
||||
* <p>The classloader inverts the standard parent-delegation model, giving preference
|
||||
* to classes defined in the source file before classes known to the parent (such
|
||||
* as any like-named classes that might be found on the application class path.)
|
||||
*/
|
||||
private static class MemoryClassLoader extends ClassLoader {
|
||||
/**
|
||||
* The map of all classes found in the source file, indexed by
|
||||
* {@link ClassLoader#name binary name}.
|
||||
*/
|
||||
private final Map<String, byte[]> sourceFileClasses;
|
||||
|
||||
/**
|
||||
* A minimal protection domain, specifying a code source of the source file itself,
|
||||
* used for classes found in the source file and defined by this loader.
|
||||
*/
|
||||
private final ProtectionDomain domain;
|
||||
|
||||
MemoryClassLoader(Map<String, byte[]> sourceFileClasses, ClassLoader parent, Path file) {
|
||||
super(parent);
|
||||
this.sourceFileClasses = sourceFileClasses;
|
||||
CodeSource codeSource;
|
||||
try {
|
||||
codeSource = new CodeSource(file.toUri().toURL(), (CodeSigner[]) null);
|
||||
} catch (MalformedURLException e) {
|
||||
codeSource = null;
|
||||
}
|
||||
domain = new ProtectionDomain(codeSource, null, this, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override loadClass to check for classes defined in the source file
|
||||
* before checking for classes in the parent class loader,
|
||||
* including those on the classpath.
|
||||
*
|
||||
* {@code loadClass(String name)} calls this method, and so will have the same behavior.
|
||||
*
|
||||
* @param name the name of the class to load
|
||||
* @param resolve whether or not to resolve the class
|
||||
* @return the class
|
||||
* @throws ClassNotFoundException if the class is not found
|
||||
*/
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
synchronized (getClassLoadingLock(name)) {
|
||||
Class<?> c = findLoadedClass(name);
|
||||
if (c == null) {
|
||||
if (sourceFileClasses.containsKey(name)) {
|
||||
c = findClass(name);
|
||||
} else {
|
||||
c = getParent().loadClass(name);
|
||||
}
|
||||
if (resolve) {
|
||||
resolveClass(c);
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Override getResource to check for resources (i.e. class files) defined in the
|
||||
* source file before checking resources in the parent class loader,
|
||||
* including those on the class path.
|
||||
*
|
||||
* {@code getResourceAsStream(String name)} calls this method,
|
||||
* and so will have the same behavior.
|
||||
*
|
||||
* @param name the name of the resource
|
||||
* @return a URL for the resource, or null if not found
|
||||
*/
|
||||
@Override
|
||||
public URL getResource(String name) {
|
||||
if (sourceFileClasses.containsKey(toBinaryName(name))) {
|
||||
return findResource(name);
|
||||
} else {
|
||||
return getParent().getResource(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override getResources to check for resources (i.e. class files) defined in the
|
||||
* source file before checking resources in the parent class loader,
|
||||
* including those on the class path.
|
||||
*
|
||||
* @param name the name of the resource
|
||||
* @return an enumeration of the resources in this loader and in the application class loader
|
||||
*/
|
||||
@Override
|
||||
public Enumeration<URL> getResources(String name) throws IOException {
|
||||
URL u = findResource(name);
|
||||
Enumeration<URL> e = getParent().getResources(name);
|
||||
if (u == null) {
|
||||
return e;
|
||||
} else {
|
||||
List<URL> list = new ArrayList<>();
|
||||
list.add(u);
|
||||
while (e.hasMoreElements()) {
|
||||
list.add(e.nextElement());
|
||||
}
|
||||
return Collections.enumeration(list);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
byte[] bytes = sourceFileClasses.get(name);
|
||||
if (bytes == null) {
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
return defineClass(name, bytes, 0, bytes.length, domain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL findResource(String name) {
|
||||
String binaryName = toBinaryName(name);
|
||||
if (binaryName == null || sourceFileClasses.get(binaryName) == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
URLStreamHandler handler = this.handler;
|
||||
if (handler == null) {
|
||||
this.handler = handler = new MemoryURLStreamHandler();
|
||||
}
|
||||
|
||||
try {
|
||||
@SuppressWarnings("deprecation")
|
||||
var result = new URL(PROTOCOL, null, -1, name, handler);
|
||||
return result;
|
||||
} catch (MalformedURLException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<URL> findResources(String name) {
|
||||
return new Enumeration<URL>() {
|
||||
private URL next = findResource(name);
|
||||
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
return (next != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL nextElement() {
|
||||
if (next == null) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
URL u = next;
|
||||
next = null;
|
||||
return u;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a "resource name" (as used in the getResource* methods)
|
||||
* to a binary name if the name identifies a class, or null otherwise.
|
||||
* @param name the resource name
|
||||
* @return the binary name
|
||||
*/
|
||||
private String toBinaryName(String name) {
|
||||
if (!name.endsWith(".class")) {
|
||||
return null;
|
||||
}
|
||||
return name.substring(0, name.length() - DOT_CLASS_LENGTH).replace('/', '.');
|
||||
}
|
||||
|
||||
private static final int DOT_CLASS_LENGTH = ".class".length();
|
||||
private final String PROTOCOL = "sourcelauncher-" + getClass().getSimpleName() + hashCode();
|
||||
private URLStreamHandler handler;
|
||||
|
||||
/**
|
||||
* A URLStreamHandler for use with URLs returned by MemoryClassLoader.getResource.
|
||||
*/
|
||||
private class MemoryURLStreamHandler extends URLStreamHandler {
|
||||
@Override
|
||||
public URLConnection openConnection(URL u) {
|
||||
if (!u.getProtocol().equalsIgnoreCase(PROTOCOL)) {
|
||||
throw new IllegalArgumentException(u.toString());
|
||||
}
|
||||
return new MemoryURLConnection(u, sourceFileClasses.get(toBinaryName(u.getPath())));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A URLConnection for use with URLs returned by MemoryClassLoader.getResource.
|
||||
*/
|
||||
private static class MemoryURLConnection extends URLConnection {
|
||||
private byte[] bytes;
|
||||
private InputStream in;
|
||||
|
||||
MemoryURLConnection(URL u, byte[] bytes) {
|
||||
super(u);
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() throws IOException {
|
||||
if (!connected) {
|
||||
if (bytes == null) {
|
||||
throw new FileNotFoundException(getURL().getPath());
|
||||
}
|
||||
in = new ByteArrayInputStream(bytes);
|
||||
connected = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
connect();
|
||||
return in;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentLengthLong() {
|
||||
return bytes.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,331 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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.ByteArrayInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.module.ModuleDescriptor;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
import java.nio.file.Files;
|
||||
import java.security.CodeSigner;
|
||||
import java.security.CodeSource;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* An in-memory classloader, that uses an in-memory cache of classes written by
|
||||
* {@link MemoryFileManager}.
|
||||
*
|
||||
* <p>The classloader inverts the standard parent-delegation model, giving preference
|
||||
* to classes defined in the source file before classes known to the parent (such
|
||||
* as any like-named classes that might be found on the application class path.)
|
||||
*
|
||||
* <p><strong>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.</strong></p>
|
||||
*/
|
||||
final class MemoryClassLoader extends ClassLoader {
|
||||
/**
|
||||
* The parent class loader instance.
|
||||
*/
|
||||
private final ClassLoader parentClassLoader;
|
||||
|
||||
/**
|
||||
* The map of all classes found in the source file, indexed by
|
||||
* {@link Class#getName()} binary name.
|
||||
*/
|
||||
private final Map<String, byte[]> sourceFileClasses;
|
||||
|
||||
/**
|
||||
* A minimal protection domain, specifying a code source of the source file itself,
|
||||
* used for classes found in the source file and defined by this loader.
|
||||
*/
|
||||
private final ProtectionDomain domain;
|
||||
|
||||
private final ModuleDescriptor moduleDescriptor;
|
||||
private final ProgramDescriptor programDescriptor;
|
||||
private final Function<String, byte[]> compileSourceFile;
|
||||
|
||||
MemoryClassLoader(Map<String, byte[]> sourceFileClasses,
|
||||
ClassLoader parentClassLoader,
|
||||
ModuleDescriptor moduleDescriptor,
|
||||
ProgramDescriptor programDescriptor,
|
||||
Function<String, byte[]> compileSourceFile) {
|
||||
super(parentClassLoader);
|
||||
this.parentClassLoader = parentClassLoader;
|
||||
this.sourceFileClasses = sourceFileClasses;
|
||||
CodeSource codeSource;
|
||||
try {
|
||||
codeSource = new CodeSource(programDescriptor.fileObject().getFile().toUri().toURL(), (CodeSigner[])null);
|
||||
} catch (MalformedURLException e) {
|
||||
codeSource = null;
|
||||
}
|
||||
domain = new ProtectionDomain(codeSource, null, this, null);
|
||||
this.moduleDescriptor = moduleDescriptor;
|
||||
this.programDescriptor = programDescriptor;
|
||||
this.compileSourceFile = compileSourceFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override loadClass to check for classes defined in the source file
|
||||
* before checking for classes in the parent class loader,
|
||||
* including those on the classpath.
|
||||
* <p>
|
||||
* {@code loadClass(String name)} calls this method, and so will have the same behavior.
|
||||
*
|
||||
* @param name the name of the class to load
|
||||
* @param resolve whether to resolve the class
|
||||
* @return the class
|
||||
* @throws ClassNotFoundException if the class is not found
|
||||
*/
|
||||
@Override
|
||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||
synchronized (getClassLoadingLock(name)) {
|
||||
Class<?> c = findLoadedClass(name);
|
||||
if (c == null) {
|
||||
c = findOrCompileClass(name);
|
||||
if (c == null) {
|
||||
c = parentClassLoader.loadClass(name);
|
||||
}
|
||||
if (resolve) {
|
||||
resolveClass(c);
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Override getResource to check for resources (i.e. class files) defined in the
|
||||
* source file before checking resources in the parent class loader,
|
||||
* including those on the class path.
|
||||
* <p>
|
||||
* {@code getResourceAsStream(String name)} calls this method,
|
||||
* and so will have the same behavior.
|
||||
*
|
||||
* @param name the name of the resource
|
||||
* @return a URL for the resource, or null if not found
|
||||
*/
|
||||
@Override
|
||||
public URL getResource(String name) {
|
||||
if (sourceFileClasses.containsKey(toBinaryName(name))) {
|
||||
return findResource(name);
|
||||
}
|
||||
var programPath = programDescriptor.sourceRootPath().resolve(name);
|
||||
if (Files.exists(programPath)) {
|
||||
try {
|
||||
return programPath.toUri().toURL();
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return parentClassLoader.getResource(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override getResources to check for resources (i.e. class files) defined in the
|
||||
* source file before checking resources in the parent class loader,
|
||||
* including those on the class path.
|
||||
*
|
||||
* @param name the name of the resource
|
||||
* @return an enumeration of the resources in this loader and in the application class loader
|
||||
*/
|
||||
@Override
|
||||
public Enumeration<URL> getResources(String name) throws IOException {
|
||||
URL u = findResource(name);
|
||||
Enumeration<URL> e = parentClassLoader.getResources(name);
|
||||
if (u == null) {
|
||||
return e;
|
||||
} else {
|
||||
List<URL> list = new ArrayList<>();
|
||||
list.add(u);
|
||||
while (e.hasMoreElements()) {
|
||||
list.add(e.nextElement());
|
||||
}
|
||||
return Collections.enumeration(list);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
var foundOrCompiledClass = findOrCompileClass(name);
|
||||
if (foundOrCompiledClass == null) {
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
return foundOrCompiledClass;
|
||||
}
|
||||
|
||||
private Class<?> findOrCompileClass(String name) {
|
||||
byte[] bytes = sourceFileClasses.get(name);
|
||||
if (bytes == null) {
|
||||
bytes = compileSourceFile.apply(name);
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return defineClass(name, bytes, 0, bytes.length, domain);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URL findResource(String moduleName, String name) throws IOException {
|
||||
if (moduleName == null) {
|
||||
return getResource(name);
|
||||
}
|
||||
if (moduleDescriptor != null && moduleDescriptor.name().equals(moduleName)) {
|
||||
return getResource(name);
|
||||
}
|
||||
return super.findResource(moduleName, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL findResource(String name) {
|
||||
String binaryName = toBinaryName(name);
|
||||
if (binaryName == null || sourceFileClasses.get(binaryName) == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
URLStreamHandler handler = this.handler;
|
||||
if (handler == null) {
|
||||
this.handler = handler = new MemoryURLStreamHandler();
|
||||
}
|
||||
|
||||
try {
|
||||
var uri = new URI(PROTOCOL, name, null);
|
||||
return URL.of(uri, handler);
|
||||
} catch (URISyntaxException | MalformedURLException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<URL> findResources(String name) {
|
||||
return new Enumeration<URL>() {
|
||||
private URL next = findResource(name);
|
||||
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
return (next != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL nextElement() {
|
||||
if (next == null) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
URL u = next;
|
||||
next = null;
|
||||
return u;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a "resource name" (as used in the getResource* methods)
|
||||
* to a binary name if the name identifies a class, or null otherwise.
|
||||
*
|
||||
* @param name the resource name
|
||||
* @return the binary name
|
||||
*/
|
||||
private String toBinaryName(String name) {
|
||||
if (!name.endsWith(".class")) {
|
||||
return null;
|
||||
}
|
||||
return name.substring(0, name.length() - DOT_CLASS_LENGTH).replace('/', '.');
|
||||
}
|
||||
|
||||
private static final int DOT_CLASS_LENGTH = ".class".length();
|
||||
private final String PROTOCOL = "sourcelauncher-" + getClass().getSimpleName() + hashCode();
|
||||
private URLStreamHandler handler;
|
||||
|
||||
/**
|
||||
* A URLStreamHandler for use with URLs returned by MemoryClassLoader.getResource.
|
||||
*/
|
||||
private class MemoryURLStreamHandler extends URLStreamHandler {
|
||||
@Override
|
||||
public URLConnection openConnection(URL u) {
|
||||
if (!u.getProtocol().equalsIgnoreCase(PROTOCOL)) {
|
||||
throw new IllegalArgumentException(u.toString());
|
||||
}
|
||||
return new MemoryURLConnection(u, sourceFileClasses.get(toBinaryName(u.getPath())));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A URLConnection for use with URLs returned by MemoryClassLoader.getResource.
|
||||
*/
|
||||
private static class MemoryURLConnection extends URLConnection {
|
||||
private final byte[] bytes;
|
||||
private InputStream in;
|
||||
|
||||
MemoryURLConnection(URL u, byte[] bytes) {
|
||||
super(u);
|
||||
this.bytes = bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() throws IOException {
|
||||
if (!connected) {
|
||||
if (bytes == null) {
|
||||
throw new FileNotFoundException(getURL().getPath());
|
||||
}
|
||||
in = new ByteArrayInputStream(bytes);
|
||||
connected = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
connect();
|
||||
return in;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentLengthLong() {
|
||||
return bytes.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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 com.sun.source.util.TaskEvent;
|
||||
import com.sun.source.util.TaskListener;
|
||||
import com.sun.tools.javac.api.JavacTool;
|
||||
import com.sun.tools.javac.code.Preview;
|
||||
import com.sun.tools.javac.file.JavacFileManager;
|
||||
import com.sun.tools.javac.resources.LauncherProperties.Errors;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
import com.sun.tools.javac.util.Context.Factory;
|
||||
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.NestingKind;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.tools.JavaFileManager;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.StandardLocation;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.module.ModuleDescriptor;
|
||||
import java.lang.module.ModuleFinder;
|
||||
import java.lang.module.ModuleReference;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An object to encapsulate the set of in-memory classes, such that
|
||||
* they can be written by a file manager and subsequently used by
|
||||
* a class loader.
|
||||
*
|
||||
* <p><strong>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.</strong></p>
|
||||
*/
|
||||
final class MemoryContext {
|
||||
private final PrintWriter out;
|
||||
private final ProgramDescriptor descriptor;
|
||||
|
||||
private final RelevantJavacOptions options;
|
||||
|
||||
private final JavacTool compiler;
|
||||
private final JavacFileManager standardFileManager;
|
||||
private final JavaFileManager memoryFileManager;
|
||||
|
||||
private final Map<String, byte[]> inMemoryClasses = new HashMap<>();
|
||||
|
||||
MemoryContext(PrintWriter out, ProgramDescriptor descriptor, RelevantJavacOptions options) throws Fault {
|
||||
this.out = out;
|
||||
this.descriptor = descriptor;
|
||||
this.options = options;
|
||||
|
||||
this.compiler = JavacTool.create();
|
||||
this.standardFileManager = compiler.getStandardFileManager(null, null, null);
|
||||
try {
|
||||
List<File> searchPath = descriptor.fileObject().isFirstLineIgnored() ? List.of() : List.of(descriptor.sourceRootPath().toFile());
|
||||
standardFileManager.setLocation(StandardLocation.SOURCE_PATH, searchPath);
|
||||
} catch (IOException e) {
|
||||
throw new Error("unexpected exception from file manager", e);
|
||||
}
|
||||
this.memoryFileManager = new MemoryFileManager(inMemoryClasses, standardFileManager);
|
||||
}
|
||||
|
||||
ProgramDescriptor getProgramDescriptor() {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
String getSourceFileAsString() {
|
||||
return descriptor.fileObject().getFile().toAbsolutePath().toString();
|
||||
}
|
||||
|
||||
Set<String> getNamesOfCompiledClasses() {
|
||||
return Set.copyOf(inMemoryClasses.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return the list of top-level types defined in the source file
|
||||
* @throws Fault if any compilation errors occur, or if no class was found
|
||||
*/
|
||||
List<String> compileProgram() throws Fault {
|
||||
var units = new ArrayList<JavaFileObject>();
|
||||
units.add(descriptor.fileObject());
|
||||
if (descriptor.isModular()) {
|
||||
var root = descriptor.sourceRootPath();
|
||||
units.add(standardFileManager.getJavaFileObject(root.resolve("module-info.java")));
|
||||
}
|
||||
var opts = options.forProgramCompilation();
|
||||
var context = new Context();
|
||||
MemoryPreview.registerInstance(context);
|
||||
var task = compiler.getTask(out, memoryFileManager, null, opts, null, units, context);
|
||||
var fileUri = descriptor.fileObject().toUri();
|
||||
var names = new ArrayList<String>();
|
||||
task.addTaskListener(new TaskListener() {
|
||||
@Override
|
||||
public void started(TaskEvent event) {
|
||||
if (event.getKind() != TaskEvent.Kind.ANALYZE) return;
|
||||
TypeElement element = event.getTypeElement();
|
||||
if (element.getNestingKind() != NestingKind.TOP_LEVEL) return;
|
||||
JavaFileObject source = event.getSourceFile();
|
||||
if (source == null) return;
|
||||
if (!source.toUri().equals(fileUri)) return;
|
||||
ElementKind kind = element.getKind();
|
||||
if (kind != ElementKind.CLASS
|
||||
&& kind != ElementKind.ENUM
|
||||
&& kind != ElementKind.INTERFACE
|
||||
&& kind != ElementKind.RECORD)
|
||||
return;
|
||||
var name = element.getQualifiedName().toString();
|
||||
names.add(name);
|
||||
}
|
||||
});
|
||||
var ok = task.call();
|
||||
if (!ok) {
|
||||
throw new Fault(Errors.CompilationFailed);
|
||||
}
|
||||
if (names.isEmpty()) {
|
||||
throw new Fault(Errors.NoClass);
|
||||
}
|
||||
return List.copyOf(names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines a source file from the given class name and compiles it.
|
||||
* Any messages generated during compilation will be written to the stream
|
||||
* provided when this object was created.
|
||||
* <p>
|
||||
* This method is passed a reference to an instance of {@link MemoryClassLoader},
|
||||
* that uses it to compile a source file on demand.
|
||||
*
|
||||
* @param name the name of the class to be compiled.
|
||||
* @return the byte code of the compiled class or {@code null}
|
||||
* if no source file was found for the given name
|
||||
*/
|
||||
byte[] compileJavaFileByName(String name) {
|
||||
// Determine source file from class name.
|
||||
var firstDollarSign = name.indexOf('$'); // [package . ] name [ $ enclosed [$ deeper] ]
|
||||
var packageAndClassName = firstDollarSign == -1 ? name : name.substring(0, firstDollarSign);
|
||||
var path = packageAndClassName.replace('.', '/') + ".java";
|
||||
var file = descriptor.sourceRootPath().resolve(path);
|
||||
|
||||
// Trivial case: no matching source file exists
|
||||
if (Files.notExists(file)) return null;
|
||||
|
||||
// Compile source file (unit) with similar options as the program.
|
||||
var opts = options.forSubsequentCompilations();
|
||||
var unit = standardFileManager.getJavaFileObject(file);
|
||||
var task = compiler.getTask(out, memoryFileManager, null, opts, null, List.of(unit));
|
||||
|
||||
var ok = task.call();
|
||||
if (!ok) {
|
||||
var fault = new Fault(Errors.CompilationFailed);
|
||||
// Don't throw fault - fail fast!
|
||||
out.println(fault.getMessage());
|
||||
System.exit(2);
|
||||
}
|
||||
|
||||
// The memory file manager stored bytes in the context map, indexed by the class names.
|
||||
return inMemoryClasses.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new class load for the main entry-point class.
|
||||
*
|
||||
* @param parent the class loader to be used as the parent loader
|
||||
* @param mainClassName the fully-qualified name of the application class to load
|
||||
* @return class loader object able to find and load the desired class
|
||||
* @throws ClassNotFoundException if the class cannot be located
|
||||
* @throws Fault if a modular application class is in the unnamed package
|
||||
*/
|
||||
ClassLoader newClassLoaderFor(ClassLoader parent, String mainClassName) throws ClassNotFoundException, Fault {
|
||||
var moduleInfoBytes = inMemoryClasses.get("module-info");
|
||||
if (moduleInfoBytes == null) {
|
||||
// Trivial case: no compiled module descriptor available, no extra module layer required
|
||||
return new MemoryClassLoader(inMemoryClasses, parent, null, descriptor, this::compileJavaFileByName);
|
||||
}
|
||||
|
||||
// Ensure main class resides in a named package.
|
||||
var lastDotInMainClassName = mainClassName.lastIndexOf('.');
|
||||
if (lastDotInMainClassName == -1) {
|
||||
throw new Fault(Errors.UnnamedPkgNotAllowedNamedModules);
|
||||
}
|
||||
|
||||
var bootLayer = ModuleLayer.boot();
|
||||
var parentLayer = bootLayer;
|
||||
var parentLoader = parent;
|
||||
|
||||
// Optionally create module layer for all modules on the module path.
|
||||
var modulePathFinder = createModuleFinderFromModulePath();
|
||||
var modulePathModules = modulePathFinder.findAll().stream().map(ModuleReference::descriptor).map(ModuleDescriptor::name).toList();
|
||||
if (!modulePathModules.isEmpty()) {
|
||||
var modulePathConfiguration = bootLayer.configuration().resolveAndBind(modulePathFinder, ModuleFinder.of(), Set.copyOf(modulePathModules));
|
||||
var modulePathLayer = ModuleLayer.defineModulesWithOneLoader(modulePathConfiguration, List.of(bootLayer), parent).layer();
|
||||
parentLayer = modulePathLayer;
|
||||
parentLoader = modulePathLayer.findLoader(modulePathModules.getFirst());
|
||||
}
|
||||
|
||||
// Create in-memory module layer for the modular application.
|
||||
var applicationModule = ModuleDescriptor.read(ByteBuffer.wrap(moduleInfoBytes), descriptor::computePackageNames);
|
||||
var memoryFinder = new MemoryModuleFinder(inMemoryClasses, applicationModule, descriptor);
|
||||
var memoryConfig = parentLayer.configuration().resolveAndBind(memoryFinder, ModuleFinder.of(), Set.of(applicationModule.name()));
|
||||
var memoryClassLoader = new MemoryClassLoader(inMemoryClasses, parentLoader, applicationModule, descriptor, this::compileJavaFileByName);
|
||||
var memoryController = ModuleLayer.defineModules(memoryConfig, List.of(parentLayer), __ -> memoryClassLoader);
|
||||
var memoryLayer = memoryController.layer();
|
||||
|
||||
// Make application class accessible from the calling (unnamed) module, that loaded this class.
|
||||
var module = memoryLayer.findModule(applicationModule.name()).orElseThrow();
|
||||
var mainClassNamePackageName = mainClassName.substring(0, lastDotInMainClassName);
|
||||
memoryController.addOpens(module, mainClassNamePackageName, getClass().getModule());
|
||||
|
||||
return memoryLayer.findLoader(applicationModule.name());
|
||||
}
|
||||
|
||||
private static ModuleFinder createModuleFinderFromModulePath() {
|
||||
var elements = System.getProperty("jdk.module.path");
|
||||
if (elements == null) {
|
||||
return ModuleFinder.of();
|
||||
}
|
||||
var paths = Arrays.stream(elements.split(File.pathSeparator)).map(Path::of);
|
||||
return ModuleFinder.of(paths.toArray(Path[]::new));
|
||||
}
|
||||
|
||||
static class MemoryPreview extends Preview {
|
||||
static void registerInstance(Context context) {
|
||||
context.put(previewKey, (Factory<Preview>)MemoryPreview::new);
|
||||
}
|
||||
|
||||
MemoryPreview(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportDeferredDiagnostics() {
|
||||
// suppress diagnostics like "Note: Recompile with -Xlint:preview for details."
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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 javax.tools.FileObject;
|
||||
import javax.tools.ForwardingJavaFileManager;
|
||||
import javax.tools.JavaFileManager;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
import javax.tools.StandardLocation;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* <p><strong>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.</strong></p>
|
||||
*/
|
||||
final 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());
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Location location, FileObject fo) throws IOException {
|
||||
return fo instanceof ProgramFileObject || super.contains(location, fo);
|
||||
}
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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.ByteArrayInputStream;
|
||||
import java.io.IOError;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.module.ModuleDescriptor;
|
||||
import java.lang.module.ModuleFinder;
|
||||
import java.lang.module.ModuleReader;
|
||||
import java.lang.module.ModuleReference;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.FileVisitOption;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jdk.internal.module.Resources;
|
||||
|
||||
/**
|
||||
* An in-memory module finder, that uses an in-memory cache of classes written by
|
||||
* {@link MemoryFileManager}.
|
||||
*
|
||||
* <p><strong>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.</strong></p>
|
||||
*/
|
||||
record MemoryModuleFinder(Map<String, byte[]> classes,
|
||||
ModuleDescriptor descriptor,
|
||||
ProgramDescriptor programDescriptor) implements ModuleFinder {
|
||||
@Override
|
||||
public Optional<ModuleReference> find(String name) {
|
||||
if (name.equals(descriptor.name())) {
|
||||
return Optional.of(new MemoryModuleReference());
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ModuleReference> findAll() {
|
||||
return Set.of(new MemoryModuleReference());
|
||||
}
|
||||
|
||||
class MemoryModuleReference extends ModuleReference {
|
||||
protected MemoryModuleReference() {
|
||||
super(descriptor, URI.create("memory:///" + descriptor.toNameAndVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModuleReader open() {
|
||||
return new MemoryModuleReader();
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation based on jdk.internal.module.ModuleReferences#ExplodedModuleReader
|
||||
class MemoryModuleReader implements ModuleReader {
|
||||
private volatile boolean closed;
|
||||
|
||||
private void ensureOpen() throws IOException {
|
||||
if (closed) {
|
||||
throw new IOException("ModuleReader is closed");
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<URI> find(String name) throws IOException {
|
||||
ensureOpen();
|
||||
// Try to find an in-memory compiled class first
|
||||
if (classes.get(name) != null) {
|
||||
return Optional.of(URI.create("memory:///" + name.replace('.', '/') + ".class"));
|
||||
}
|
||||
// Try to find file resource from root path next
|
||||
Path path = Resources.toFilePath(programDescriptor.sourceRootPath(), name);
|
||||
if (path != null) {
|
||||
try {
|
||||
return Optional.of(path.toUri());
|
||||
} catch (IOError error) {
|
||||
throw (IOException) error.getCause();
|
||||
}
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<InputStream> open(String name) throws IOException {
|
||||
ensureOpen();
|
||||
// Try to find an in-memory compiled class first
|
||||
byte[] bytes = classes.get(name);
|
||||
if (bytes != null) {
|
||||
return Optional.of(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
// Try to find file resource from root path next
|
||||
Path path = Resources.toFilePath(programDescriptor.sourceRootPath(), name);
|
||||
return path != null ? Optional.of(Files.newInputStream(path)) : Optional.empty();
|
||||
}
|
||||
|
||||
public Optional<ByteBuffer> read(String name) throws IOException {
|
||||
ensureOpen();
|
||||
// Try to find an in-memory compiled class first
|
||||
byte[] bytes = classes.get(name);
|
||||
if (bytes != null) {
|
||||
return Optional.of(ByteBuffer.wrap(bytes));
|
||||
}
|
||||
// Try to find file resource from root path next
|
||||
Path path = Resources.toFilePath(programDescriptor.sourceRootPath(), name);
|
||||
return path != null ? Optional.of(ByteBuffer.wrap(Files.readAllBytes(path))) : Optional.empty();
|
||||
}
|
||||
|
||||
public Stream<String> list() throws IOException {
|
||||
ensureOpen();
|
||||
var root = programDescriptor.sourceRootPath();
|
||||
var list = new ArrayList<String>();
|
||||
classes.keySet().stream().map(name -> name.replace('.', '/') + ".class").forEach(list::add);
|
||||
try (var stream = Files.walk(root, Integer.MAX_VALUE, new FileVisitOption[0])) {
|
||||
stream
|
||||
.map(file -> Resources.toResourceName(root, file))
|
||||
.filter(name -> !name.isEmpty())
|
||||
.forEach(list::add);
|
||||
}
|
||||
Collections.sort(list);
|
||||
return list.stream();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
this.closed = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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 com.sun.tools.javac.api.JavacTool;
|
||||
import com.sun.tools.javac.resources.LauncherProperties.Errors;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Describes a launch-able Java compilation unit.
|
||||
*
|
||||
* <p><strong>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.</strong></p>
|
||||
*/
|
||||
public record ProgramDescriptor(ProgramFileObject fileObject, Optional<String> packageName, Path sourceRootPath) {
|
||||
static ProgramDescriptor of(ProgramFileObject fileObject) throws Fault {
|
||||
var file = fileObject.getFile();
|
||||
try {
|
||||
var compiler = JavacTool.create();
|
||||
var standardFileManager = compiler.getStandardFileManager(null, null, null);
|
||||
var units = List.of(fileObject);
|
||||
var task = compiler.getTask(null, standardFileManager, diagnostic -> {}, null, null, units);
|
||||
for (var tree : task.parse()) {
|
||||
var packageTree = tree.getPackage();
|
||||
if (packageTree != null) {
|
||||
var packageName = packageTree.getPackageName().toString();
|
||||
var root = computeSourceRootPath(file, packageName);
|
||||
return new ProgramDescriptor(fileObject, Optional.of(packageName), root);
|
||||
}
|
||||
}
|
||||
} catch (IOException ignore) {
|
||||
// fall through to let actual compilation determine the error message
|
||||
}
|
||||
var root = computeSourceRootPath(file, "");
|
||||
return new ProgramDescriptor(fileObject, Optional.empty(), root);
|
||||
}
|
||||
|
||||
public static Path computeSourceRootPath(Path program, String packageName) {
|
||||
var absolute = program.normalize().toAbsolutePath();
|
||||
var absoluteRoot = absolute.getRoot();
|
||||
assert absoluteRoot != null;
|
||||
// unnamed package "": program's directory is the root path
|
||||
if (packageName.isEmpty()) {
|
||||
var parent = absolute.getParent();
|
||||
if (parent == null) return absoluteRoot;
|
||||
return parent;
|
||||
}
|
||||
// named package "a.b.c": ensure end of path to program is "a/b/c"
|
||||
var packagePath = Path.of(packageName.replace('.', '/'));
|
||||
var ending = packagePath.resolve(program.getFileName());
|
||||
if (absolute.endsWith(ending)) {
|
||||
var max = absolute.getNameCount() - ending.getNameCount();
|
||||
if (max == 0) return absoluteRoot;
|
||||
return absoluteRoot.resolve(absolute.subpath(0, max));
|
||||
}
|
||||
throw new Fault(Errors.MismatchEndOfPathAndPackageName(packageName, program));
|
||||
}
|
||||
|
||||
public boolean isModular() {
|
||||
return Files.exists(sourceRootPath.resolve("module-info.java"));
|
||||
}
|
||||
|
||||
public Set<String> computePackageNames() {
|
||||
try (var stream = Files.find(sourceRootPath, 99, (path, attr) -> attr.isDirectory())) {
|
||||
var names = new TreeSet<String>();
|
||||
stream.filter(ProgramDescriptor::containsAtLeastOneRegularFile)
|
||||
.map(sourceRootPath::relativize)
|
||||
.map(Path::toString)
|
||||
.filter(string -> !string.isEmpty())
|
||||
.map(string -> string.replace(File.separatorChar, '.'))
|
||||
.forEach(names::add);
|
||||
return names;
|
||||
} catch (IOException exception) {
|
||||
throw new UncheckedIOException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean containsAtLeastOneRegularFile(Path directory) {
|
||||
try (var stream = Files.newDirectoryStream(directory, Files::isRegularFile)) {
|
||||
return stream.iterator().hasNext();
|
||||
} catch (IOException exception) {
|
||||
throw new UncheckedIOException(exception);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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 com.sun.tools.javac.resources.LauncherProperties.Errors;
|
||||
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static javax.tools.JavaFileObject.Kind.SOURCE;
|
||||
|
||||
/**
|
||||
* The program to launch as Java file object.
|
||||
*
|
||||
* <p><strong>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.</strong></p>
|
||||
*/
|
||||
final class ProgramFileObject extends SimpleJavaFileObject {
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
static ProgramFileObject of(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(System.lineSeparator()); // 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 ProgramFileObject(file, sb, ignoreFirstLine);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new Fault(Errors.CantReadFile(file, e));
|
||||
}
|
||||
}
|
||||
|
||||
private final Path file;
|
||||
private final CharSequence chars;
|
||||
private final boolean ignoreFirstLine;
|
||||
|
||||
ProgramFileObject(Path file, CharSequence chars, boolean ignoreFirstLine) {
|
||||
super(file.toUri(), SOURCE);
|
||||
this.file = file;
|
||||
this.chars = chars;
|
||||
this.ignoreFirstLine = ignoreFirstLine;
|
||||
}
|
||||
|
||||
public Path getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public boolean isFirstLineIgnored() {
|
||||
return ignoreFirstLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return file.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
|
||||
return chars;
|
||||
}
|
||||
|
||||
@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 + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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 com.sun.tools.javac.code.Source;
|
||||
import com.sun.tools.javac.main.Option;
|
||||
import com.sun.tools.javac.resources.LauncherProperties.Errors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents runtime arguments that are relevant to {@code javac}.
|
||||
*
|
||||
* <p><strong>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.</strong></p>
|
||||
*/
|
||||
record RelevantJavacOptions(List<String> forProgramCompilation,
|
||||
List<String> forSubsequentCompilations) {
|
||||
|
||||
/**
|
||||
* 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 program the program descriptor
|
||||
* @param runtimeArgs the runtime arguments
|
||||
* @return the subset of the runtime arguments
|
||||
*/
|
||||
static RelevantJavacOptions of(ProgramDescriptor program, String... runtimeArgs) throws Fault {
|
||||
var programOptions = new ArrayList<String>();
|
||||
var subsequentOptions = new ArrayList<String>();
|
||||
|
||||
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));
|
||||
}
|
||||
programOptions.addAll(List.of("--release", sourceOpt));
|
||||
subsequentOptions.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", "-classpath", "-cp",
|
||||
"--module-path", "-p",
|
||||
"--add-exports", "--add-modules",
|
||||
"--limit-modules",
|
||||
"--patch-module",
|
||||
"--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")) {
|
||||
var modules = computeListOfAddModules(program, value);
|
||||
if (modules.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
value = String.join(",", modules);
|
||||
}
|
||||
programOptions.add(opt);
|
||||
programOptions.add(value);
|
||||
var javacOption = Option.lookup(opt);
|
||||
if (javacOption != null && javacOption.isInBasicOptionGroup()) {
|
||||
subsequentOptions.add(opt);
|
||||
subsequentOptions.add(value);
|
||||
}
|
||||
}
|
||||
case "--enable-preview" -> {
|
||||
programOptions.add(opt);
|
||||
subsequentOptions.add(opt);
|
||||
if (sourceOpt == null) {
|
||||
throw new Fault(Errors.EnablePreviewRequiresSource);
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
if (opt.startsWith("-agentlib:jdwp=") || opt.startsWith("-Xrunjdwp:")) {
|
||||
programOptions.add("-g");
|
||||
subsequentOptions.add("-g");
|
||||
}
|
||||
}
|
||||
// ignore all other runtime args
|
||||
}
|
||||
}
|
||||
|
||||
// add implicit options to both lists
|
||||
var implicitOptions = """
|
||||
-proc:none
|
||||
-implicit:none
|
||||
-Xprefer:source
|
||||
-Xdiags:verbose
|
||||
-Xlint:deprecation
|
||||
-Xlint:unchecked
|
||||
-Xlint:-options
|
||||
-XDsourceLauncher
|
||||
""";
|
||||
implicitOptions.lines()
|
||||
.filter(line -> !line.isBlank())
|
||||
.forEach(option -> {
|
||||
programOptions.add(option);
|
||||
subsequentOptions.add(option);
|
||||
});
|
||||
|
||||
return new RelevantJavacOptions(List.copyOf(programOptions), List.copyOf(subsequentOptions));
|
||||
}
|
||||
|
||||
private static List<String> computeListOfAddModules(ProgramDescriptor program, String value) {
|
||||
var modules = new ArrayList<>(List.of(value.split(",")));
|
||||
// these options are only supported at run time;
|
||||
// they are not required or supported at compile time
|
||||
modules.remove("ALL-DEFAULT");
|
||||
modules.remove("ALL-SYSTEM");
|
||||
|
||||
// ALL-MODULE-PATH can only be used when compiling the
|
||||
// unnamed module or when compiling in the context of
|
||||
// an automatic module
|
||||
if (program.isModular()) {
|
||||
modules.remove("ALL-MODULE-PATH");
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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.util.Set;
|
||||
|
||||
/**
|
||||
* Contains information about the launched program.
|
||||
*
|
||||
* <p><strong>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.</strong></p>
|
||||
*
|
||||
* @param programClass the class instance of the launched program.
|
||||
* @param classNames the names of classes compiled into memory.
|
||||
*/
|
||||
public record Result(Class<?> programClass, Set<String> classNames) {}
|
@ -0,0 +1,279 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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 com.sun.tools.javac.resources.LauncherProperties.Errors;
|
||||
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import jdk.internal.misc.MethodFinder;
|
||||
import jdk.internal.misc.VM;
|
||||
|
||||
/**
|
||||
* Compiles a source file, and executes the main method it contains.
|
||||
*
|
||||
* <p><strong>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.</strong></p>
|
||||
*/
|
||||
public final class SourceLauncher {
|
||||
/**
|
||||
* 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 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 SourceLauncher(System.err)
|
||||
.checkSecurityManager()
|
||||
.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.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
/** Stream for reporting errors, such as compilation errors. */
|
||||
private final PrintWriter out;
|
||||
|
||||
/**
|
||||
* Creates an instance of this class, providing a stream to which to report
|
||||
* any errors.
|
||||
*
|
||||
* @param out the stream
|
||||
*/
|
||||
public SourceLauncher(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 SourceLauncher(PrintWriter out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a security manager is present and throws an exception if so.
|
||||
* @return this object
|
||||
* @throws Fault if a security manager is present
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
private SourceLauncher checkSecurityManager() throws Fault {
|
||||
if (System.getSecurityManager() != null) {
|
||||
throw new Fault(Errors.SecurityManager);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Result run(String[] runtimeArgs, String[] args) throws Fault, InvocationTargetException {
|
||||
Path file = getFile(args);
|
||||
|
||||
ProgramDescriptor program = ProgramDescriptor.of(ProgramFileObject.of(file));
|
||||
RelevantJavacOptions options = RelevantJavacOptions.of(program, runtimeArgs);
|
||||
MemoryContext context = new MemoryContext(out, program, options);
|
||||
List<String> names = context.compileProgram();
|
||||
|
||||
String[] mainArgs = Arrays.copyOfRange(args, 1, args.length);
|
||||
var appClass = execute(names, mainArgs, context);
|
||||
|
||||
return new Result(appClass, context.getNamesOfCompiledClasses());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path for the filename found in the first of an array of arguments.
|
||||
*
|
||||
* @param args the array
|
||||
* @return the path, as given in the array of args
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the {@code main} method of a program class, using a class loader that
|
||||
* will load recently compiled classes from memory.
|
||||
*
|
||||
* @param topLevelClassNames the names of classes in the program compilation unit
|
||||
* @param mainArgs 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 Class<?> execute(List<String> topLevelClassNames, String[] mainArgs, MemoryContext context)
|
||||
throws Fault, InvocationTargetException {
|
||||
System.setProperty("jdk.launcher.sourcefile", context.getSourceFileAsString());
|
||||
ClassLoader parentLoader = ClassLoader.getSystemClassLoader();
|
||||
|
||||
// 1. Find a main method in the first class and if there is one - invoke it
|
||||
Class<?> firstClass;
|
||||
String firstClassName = topLevelClassNames.getFirst();
|
||||
try {
|
||||
ClassLoader loader = context.newClassLoaderFor(parentLoader, firstClassName);
|
||||
firstClass = Class.forName(firstClassName, false, loader);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new Fault(Errors.CantFindClass(firstClassName));
|
||||
}
|
||||
|
||||
Method mainMethod = MethodFinder.findMainMethod(firstClass);
|
||||
if (mainMethod == null) {
|
||||
// 2. If the first class doesn't have a main method, look for a class with a matching name
|
||||
var compilationUnitName = context.getProgramDescriptor().fileObject().getFile().getFileName().toString();
|
||||
assert compilationUnitName.endsWith(".java");
|
||||
var expectedName = compilationUnitName.substring(0, compilationUnitName.length() - 5);
|
||||
var actualName = topLevelClassNames.stream()
|
||||
.filter(name -> name.equals(expectedName))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new Fault(Errors.CantFindClass(expectedName)));
|
||||
|
||||
Class<?> actualClass;
|
||||
try {
|
||||
actualClass = Class.forName(actualName, false, firstClass.getClassLoader());
|
||||
} catch (ClassNotFoundException ignore) {
|
||||
throw new Fault(Errors.CantFindClass(actualName));
|
||||
}
|
||||
mainMethod = MethodFinder.findMainMethod(actualClass);
|
||||
if (mainMethod == null) {
|
||||
throw new Fault(Errors.CantFindMainMethod(actualName));
|
||||
}
|
||||
}
|
||||
|
||||
// selected main method instance points back to its declaring class
|
||||
Class<?> mainClass = mainMethod.getDeclaringClass();
|
||||
String mainClassName = mainClass.getName();
|
||||
|
||||
var isStatic = Modifier.isStatic(mainMethod.getModifiers());
|
||||
|
||||
Object instance = null;
|
||||
|
||||
if (!isStatic) {
|
||||
Constructor<?> constructor;
|
||||
try {
|
||||
constructor = mainClass.getDeclaredConstructor();
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new Fault(Errors.CantFindConstructor(mainClassName));
|
||||
}
|
||||
|
||||
try {
|
||||
constructor.setAccessible(true);
|
||||
instance = constructor.newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new Fault(Errors.CantAccessConstructor(mainClassName));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Similar to sun.launcher.LauncherHelper#executeMainClass
|
||||
// but duplicated here to prevent additional launcher frames
|
||||
mainMethod.setAccessible(true);
|
||||
Object receiver = isStatic ? mainClass : instance;
|
||||
|
||||
if (mainMethod.getParameterCount() == 0) {
|
||||
mainMethod.invoke(receiver);
|
||||
} else {
|
||||
mainMethod.invoke(receiver, (Object)mainArgs);
|
||||
}
|
||||
} 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.getCause();
|
||||
StackTraceElement[] targetTrace = target.getStackTrace();
|
||||
target.setStackTrace(Arrays.copyOfRange(targetTrace, 0, targetTrace.length - invocationFrames));
|
||||
throw e;
|
||||
}
|
||||
|
||||
return mainClass;
|
||||
}
|
||||
}
|
@ -83,6 +83,10 @@ import static com.sun.tools.javac.main.Option.OptionKind.*;
|
||||
* {@code handleOption} then calls {@link #process process} providing a suitable
|
||||
* {@link OptionHelper} to provide access the compiler state.
|
||||
*
|
||||
* <p>A subset of options is relevant to the source launcher implementation
|
||||
* located in {@link com.sun.tools.javac.launcher} package. When an option is
|
||||
* added, changed, or removed, also update the {@code RelevantJavacOptions} class
|
||||
* in the launcher package accordingly.
|
||||
*
|
||||
* <p>Maintenance note: when adding new annotation processing related
|
||||
* options, the list of options regarded as requesting explicit
|
||||
@ -1081,6 +1085,10 @@ public enum Option {
|
||||
return kind;
|
||||
}
|
||||
|
||||
public boolean isInBasicOptionGroup() {
|
||||
return group == BASIC;
|
||||
}
|
||||
|
||||
public ArgKind getArgKind() {
|
||||
return argKind;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
# Copyright (c) 2018, 2023, 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
|
||||
@ -141,3 +141,10 @@ launcher.err.invalid.value.for.source=\
|
||||
|
||||
launcher.err.enable.preview.requires.source=\
|
||||
--enable-preview must be used with --source
|
||||
|
||||
launcher.err.unnamed.pkg.not.allowed.named.modules=\
|
||||
unnamed package is not allowed in named modules
|
||||
|
||||
# 0: string, 1: path
|
||||
launcher.err.mismatch.end.of.path.and.package.name=\
|
||||
end of path to source file does not match its package name {0}: {1}
|
||||
|
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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.
|
||||
*/
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @enablePreview
|
||||
* @bug 8304400
|
||||
* @summary Test basic features of javac's source-code launcher
|
||||
* @modules jdk.compiler/com.sun.tools.javac.launcher
|
||||
* @run junit BasicSourceLauncherTests
|
||||
*/
|
||||
class BasicSourceLauncherTests {
|
||||
@Test
|
||||
void launchHelloClassInHelloJavaUnit(@TempDir Path base) throws Exception {
|
||||
var hello = Files.writeString(base.resolve("Hello.java"),
|
||||
"""
|
||||
public class Hello {
|
||||
public static void main(String... args) {
|
||||
System.out.println("Hi");
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
var run = Run.of(hello);
|
||||
var result = run.result();
|
||||
assertAll("# " + run,
|
||||
() -> assertLinesMatch(
|
||||
"""
|
||||
Hi
|
||||
""".lines(), run.stdOut().lines()),
|
||||
() -> assertTrue(run.stdErr().isEmpty()),
|
||||
() -> assertNull(run.exception()),
|
||||
() -> assertEquals(Set.of("Hello"), result.classNames()),
|
||||
() -> assertNotNull(result.programClass().getResource("Hello.java")),
|
||||
() -> assertNotNull(result.programClass().getResource("Hello.class")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void launchHelloClassInHalloJavaUnit(@TempDir Path base) throws Exception {
|
||||
var hallo = Files.writeString(base.resolve("Hallo.java"),
|
||||
"""
|
||||
public class Hello {
|
||||
public static void main(String... args) {
|
||||
System.out.println("Hi!");
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
var run = Run.of(hallo);
|
||||
var result = run.result();
|
||||
assertAll("# " + run,
|
||||
() -> assertLinesMatch(
|
||||
"""
|
||||
Hi!
|
||||
""".lines(), run.stdOut().lines()),
|
||||
() -> assertTrue(run.stdErr().isEmpty()),
|
||||
() -> assertNull(run.exception()),
|
||||
() -> assertEquals(Set.of("Hello"), result.classNames()),
|
||||
() -> assertNotNull(result.programClass().getResource("Hallo.java")),
|
||||
() -> assertNotNull(result.programClass().getResource("Hello.class")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void launchMinifiedJavaProgram(@TempDir Path base) throws Exception {
|
||||
var hi = Files.writeString(base.resolve("Hi.java"),
|
||||
"""
|
||||
void main() {
|
||||
System.out.println("Hi!");
|
||||
}
|
||||
""");
|
||||
|
||||
// Replace with plain Run.of(hi) once implict classes are out of preview
|
||||
System.setProperty("jdk.internal.javac.source", String.valueOf(Runtime.version().feature()));
|
||||
var run = Run.of(hi, List.of("--enable-preview"), List.of());
|
||||
System.clearProperty("jdk.internal.javac.source");
|
||||
|
||||
assertAll("# " + run,
|
||||
() -> assertLinesMatch(
|
||||
"""
|
||||
Hi!
|
||||
""".lines(), run.stdOut().lines()),
|
||||
() -> assertTrue(run.stdErr().isEmpty()),
|
||||
() -> assertNull(run.exception()));
|
||||
}
|
||||
}
|
@ -40,7 +40,7 @@ import toolbox.Task;
|
||||
import toolbox.ToolBox;
|
||||
|
||||
/*
|
||||
* The body of this test is in ${test.src}/src/CLTest.java,
|
||||
* The body of this test is in ${test.src}/src/p/q/CLTest.java,
|
||||
* which is executed in single-file source-launcher mode,
|
||||
* in order to test the classloader used to launch such programs.
|
||||
*/
|
||||
@ -52,7 +52,7 @@ public class GetResourceTest {
|
||||
|
||||
void run() throws Exception {
|
||||
ToolBox tb = new ToolBox();
|
||||
Path file = Paths.get(tb.testSrc).resolve("src").resolve("CLTest.java");
|
||||
Path file = Paths.get(tb.testSrc).resolve("src/p/q").resolve("CLTest.java");
|
||||
new JavaTask(tb)
|
||||
.vmOptions("--enable-preview", "--source", String.valueOf(Runtime.version().feature()))
|
||||
.className(file.toString()) // implies source file mode
|
||||
|
@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8304400
|
||||
* @summary Test source launcher running Java programs contained in one module
|
||||
* @modules jdk.compiler/com.sun.tools.javac.launcher
|
||||
* @run junit ModuleSourceLauncherTests
|
||||
*/
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.spi.ToolProvider;
|
||||
|
||||
class ModuleSourceLauncherTests {
|
||||
@Test
|
||||
void testHelloModularWorld(@TempDir Path base) throws Exception {
|
||||
var packageFolder = Files.createDirectories(base.resolve("com/greetings"));
|
||||
var mainFile = Files.writeString(packageFolder.resolve("Main.java"),
|
||||
"""
|
||||
package com.greetings;
|
||||
public class Main {
|
||||
public static void main(String... args) {
|
||||
System.out.println("Greetings!");
|
||||
System.out.println(" module -> " + Main.class.getModule().getName());
|
||||
System.out.println(" package -> " + Main.class.getPackageName());
|
||||
System.out.println(" class -> " + Main.class.getSimpleName());
|
||||
}
|
||||
}
|
||||
""");
|
||||
Files.writeString(base.resolve("module-info.java"),
|
||||
"""
|
||||
module com.greetings {}
|
||||
""");
|
||||
|
||||
var run = Run.of(mainFile);
|
||||
assertAll("Run -> " + run,
|
||||
() -> assertLinesMatch(
|
||||
"""
|
||||
Greetings!
|
||||
module -> com.greetings
|
||||
package -> com.greetings
|
||||
class -> Main
|
||||
""".lines(),
|
||||
run.stdOut().lines()),
|
||||
() -> assertTrue(run.stdErr().isEmpty()),
|
||||
() -> assertNull(run.exception())
|
||||
);
|
||||
|
||||
var module = run.result().programClass().getModule();
|
||||
assertEquals("com.greetings", module.getName());
|
||||
var reference = module.getLayer().configuration().findModule(module.getName()).orElseThrow().reference();
|
||||
try (var reader = reference.open()) {
|
||||
assertLinesMatch(
|
||||
"""
|
||||
com/
|
||||
com/greetings/
|
||||
com/greetings/Main.class
|
||||
com/greetings/Main.java
|
||||
module-info.class
|
||||
module-info.java
|
||||
""".lines(),
|
||||
reader.list());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTwoAndHalfPackages(@TempDir Path base) throws Exception {
|
||||
var fooFolder = Files.createDirectories(base.resolve("foo"));
|
||||
var program = Files.writeString(fooFolder.resolve("Main.java"),
|
||||
"""
|
||||
package foo;
|
||||
public class Main {
|
||||
public static void main(String... args) throws Exception {
|
||||
var module = Main.class.getModule();
|
||||
System.out.println("To the " + bar.Bar.class + " from " + module);
|
||||
try (var stream = module.getResourceAsStream("baz/baz.txt")) {
|
||||
System.out.println(new String(stream.readAllBytes()));
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
var barFolder = Files.createDirectories(base.resolve("bar"));
|
||||
Files.writeString(barFolder.resolve("Bar.java"), "package bar; public record Bar() {}");
|
||||
var bazFolder = Files.createDirectories(base.resolve("baz"));
|
||||
Files.writeString(bazFolder.resolve("baz.txt"), "baz");
|
||||
|
||||
Files.writeString(base.resolve("module-info.java"),
|
||||
"""
|
||||
module m {
|
||||
exports foo;
|
||||
exports bar;
|
||||
opens baz;
|
||||
}
|
||||
""");
|
||||
|
||||
var run = Run.of(program);
|
||||
var result = run.result();
|
||||
assertAll("Run -> " + run,
|
||||
() -> assertLinesMatch(
|
||||
"""
|
||||
To the class bar.Bar from module m
|
||||
baz
|
||||
""".lines(),
|
||||
run.stdOut().lines()),
|
||||
() -> assertTrue(run.stdErr().isEmpty()),
|
||||
() -> assertNull(run.exception()),
|
||||
() -> assertEquals(Set.of("foo", "bar", "baz"), result.programClass().getModule().getPackages())
|
||||
);
|
||||
|
||||
var module = run.result().programClass().getModule();
|
||||
assertEquals("m", module.getName());
|
||||
var reference = module.getLayer().configuration().findModule(module.getName()).orElseThrow().reference();
|
||||
try (var reader = reference.open()) {
|
||||
assertLinesMatch(
|
||||
"""
|
||||
bar/
|
||||
bar/Bar.class
|
||||
bar/Bar.java
|
||||
baz/
|
||||
baz/baz.txt
|
||||
foo/
|
||||
foo/Main.class
|
||||
foo/Main.java
|
||||
module-info.class
|
||||
module-info.java
|
||||
""".lines(),
|
||||
reader.list());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUserModuleOnModulePath(@TempDir Path base) throws Exception {
|
||||
Files.createDirectories(base.resolve("foo", "foo"));
|
||||
Files.writeString(base.resolve("foo", "module-info.java"),
|
||||
"""
|
||||
module foo {
|
||||
exports foo;
|
||||
}
|
||||
""");
|
||||
Files.writeString(base.resolve("foo", "foo", "Foo.java"),
|
||||
"""
|
||||
package foo;
|
||||
public record Foo() {}
|
||||
""");
|
||||
var javac = ToolProvider.findFirst("javac").orElseThrow();
|
||||
javac.run(System.out, System.err, "--module-source-path", base.toString(), "--module", "foo", "-d", base.toString());
|
||||
|
||||
Files.createDirectories(base.resolve("bar", "bar"));
|
||||
Files.writeString(base.resolve("bar", "module-info.java"),
|
||||
"""
|
||||
module bar {
|
||||
requires foo;
|
||||
}
|
||||
""");
|
||||
Files.writeString(base.resolve("bar", "bar","Prog1.java"),
|
||||
"""
|
||||
package bar;
|
||||
class Prog1 {
|
||||
public static void main(String... args) {
|
||||
System.out.println(new foo.Foo());
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
var command = List.of(
|
||||
Path.of(System.getProperty("java.home"), "bin", "java").toString(),
|
||||
"-p", ".",
|
||||
"bar/bar/Prog1.java");
|
||||
var redirectedOut = base.resolve("out.redirected");
|
||||
var redirectedErr = base.resolve("err.redirected");
|
||||
var process = new ProcessBuilder(command)
|
||||
.directory(base.toFile())
|
||||
.redirectOutput(redirectedOut.toFile())
|
||||
.redirectError(redirectedErr.toFile())
|
||||
.start();
|
||||
var code = process.waitFor();
|
||||
var out = Files.readAllLines(redirectedOut);
|
||||
var err = Files.readAllLines(redirectedErr);
|
||||
|
||||
assertAll(
|
||||
() -> assertEquals(0, code),
|
||||
() -> assertLinesMatch(
|
||||
"""
|
||||
Foo[]
|
||||
""".lines(), out.stream()),
|
||||
() -> assertTrue(err.isEmpty())
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8304400
|
||||
* @summary Test source launcher running Java programs spanning multiple files
|
||||
* @modules jdk.compiler/com.sun.tools.javac.launcher
|
||||
* @run junit MultiFileSourceLauncherTests
|
||||
*/
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import com.sun.tools.javac.launcher.Fault;
|
||||
import java.nio.file.*;
|
||||
import org.junit.jupiter.api.*;
|
||||
import org.junit.jupiter.api.io.*;
|
||||
|
||||
class MultiFileSourceLauncherTests {
|
||||
@Test
|
||||
void testHelloWorldInTwoCompilationUnits(@TempDir Path base) throws Exception {
|
||||
var hello = Files.writeString(base.resolve("Hello.java"),
|
||||
"""
|
||||
public class Hello {
|
||||
public static void main(String... args) {
|
||||
System.out.println("Hello " + new World("Terra"));
|
||||
System.out.println(Hello.class.getResource("Hello.java"));
|
||||
System.out.println(Hello.class.getResource("World.java"));
|
||||
}
|
||||
}
|
||||
""");
|
||||
Files.writeString(base.resolve("World.java"),
|
||||
"""
|
||||
record World(String name) {}
|
||||
""");
|
||||
|
||||
var run = Run.of(hello);
|
||||
assertLinesMatch(
|
||||
"""
|
||||
Hello World[name=Terra]
|
||||
\\Qfile:\\E.+\\QHello.java\\E
|
||||
\\Qfile:\\E.+\\QWorld.java\\E
|
||||
""".lines(),
|
||||
run.stdOut().lines());
|
||||
assertTrue(run.stdErr().isEmpty(), run.toString());
|
||||
assertNull(run.exception(), run.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadingOfEnclosedTypes(@TempDir Path base) throws Exception {
|
||||
var hello = Files.writeString(base.resolve("Hello.java"),
|
||||
"""
|
||||
public class Hello {
|
||||
public static void main(String... args) throws Exception {
|
||||
System.out.println(Class.forName("World$Core"));
|
||||
System.out.println(Class.forName("p.q.Unit$First$Second"));
|
||||
}
|
||||
}
|
||||
""");
|
||||
Files.writeString(base.resolve("World.java"),
|
||||
"""
|
||||
record World(String name) {
|
||||
record Core() {}
|
||||
}
|
||||
""");
|
||||
var pq = Files.createDirectories(base.resolve("p/q"));
|
||||
Files.writeString(pq.resolve("Unit.java"),
|
||||
"""
|
||||
package p.q;
|
||||
record Unit() {
|
||||
record First() {
|
||||
record Second() {}
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
var run = Run.of(hello);
|
||||
assertAll("Run -> " + run,
|
||||
() -> assertLinesMatch(
|
||||
"""
|
||||
class World$Core
|
||||
class p.q.Unit$First$Second
|
||||
""".lines(),
|
||||
run.stdOut().lines()),
|
||||
() -> assertTrue(run.stdErr().isEmpty()),
|
||||
() -> assertNull(run.exception())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultiplePackages(@TempDir Path base) throws Exception {
|
||||
var packageA = Files.createDirectories(base.resolve("a"));
|
||||
var hello = Files.writeString(packageA.resolve("Hello.java"),
|
||||
"""
|
||||
package a;
|
||||
import b.World;
|
||||
public class Hello {
|
||||
public static void main(String... args) {
|
||||
System.out.println("Hello " + new World("in package b"));
|
||||
}
|
||||
}
|
||||
""");
|
||||
var packageB = Files.createDirectories(base.resolve("b"));
|
||||
Files.writeString(packageB.resolve("World.java"),
|
||||
"""
|
||||
package b;
|
||||
public record World(String name) {}
|
||||
""");
|
||||
|
||||
var run = Run.of(hello);
|
||||
assertLinesMatch(
|
||||
"""
|
||||
Hello World[name=in package b]
|
||||
""".lines(),
|
||||
run.stdOut().lines());
|
||||
assertTrue(run.stdErr().isEmpty(), run.toString());
|
||||
assertNull(run.exception(), run.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMissingSecondUnit(@TempDir Path base) throws Exception {
|
||||
var program = Files.writeString(base.resolve("Program.java"),
|
||||
"""
|
||||
public class Program {
|
||||
public static void main(String... args) {
|
||||
System.out.println("Hello " + new MissingSecondUnit());
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
var run = Run.of(program);
|
||||
assertTrue(run.stdOut().isEmpty(), run.toString());
|
||||
assertLinesMatch(
|
||||
"""
|
||||
%s:3: error: cannot find symbol
|
||||
System.out.println("Hello " + new MissingSecondUnit());
|
||||
^
|
||||
symbol: class MissingSecondUnit
|
||||
location: class Program
|
||||
1 error
|
||||
""".formatted(program.toString())
|
||||
.lines(),
|
||||
run.stdErr().lines(),
|
||||
run.toString());
|
||||
assertTrue(run.exception() instanceof Fault);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSecondUnitWithSyntaxError(@TempDir Path base) throws Exception {
|
||||
var program = Files.writeString(base.resolve("Program.java"),
|
||||
"""
|
||||
public class Program {
|
||||
public static void main(String... args) {
|
||||
System.out.println("Hello " + new BrokenSecondUnit());
|
||||
}
|
||||
}
|
||||
""");
|
||||
var broken = Files.writeString(base.resolve("BrokenSecondUnit.java"),
|
||||
"""
|
||||
record BrokenSecondUnit {}
|
||||
""");
|
||||
|
||||
var run = Run.of(program);
|
||||
assertTrue(run.stdOut().isEmpty(), run.toString());
|
||||
assertLinesMatch(
|
||||
"""
|
||||
%s:1: error: '(' expected
|
||||
>> MORE LINES >>
|
||||
""".formatted(broken.toString())
|
||||
.lines(),
|
||||
run.stdErr().lines(),
|
||||
run.toString());
|
||||
assertTrue(run.exception() instanceof Fault);
|
||||
}
|
||||
|
||||
@Test
|
||||
void onlyJavaFilesReferencedByTheProgramAreCompiled(@TempDir Path base) throws Exception {
|
||||
var prog = Files.writeString(base.resolve("Prog.java"),
|
||||
"""
|
||||
class Prog {
|
||||
public static void main(String... args) {
|
||||
Helper.run();
|
||||
}
|
||||
}
|
||||
""");
|
||||
Files.writeString(base.resolve("Helper.java"),
|
||||
"""
|
||||
class Helper {
|
||||
static void run() {
|
||||
System.out.println("Hello!");
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
var old = Files.writeString(base.resolve("OldProg.java"),
|
||||
"""
|
||||
class OldProg {
|
||||
public static void main(String... args) {
|
||||
Helper.run()
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
var run = Run.of(prog);
|
||||
assertAll("Run := " + run,
|
||||
() -> assertLinesMatch(
|
||||
"""
|
||||
Hello!
|
||||
""".lines(), run.stdOut().lines()),
|
||||
() -> assertTrue(run.stdErr().isEmpty()),
|
||||
() -> assertNull(run.exception()));
|
||||
|
||||
var fail = Run.of(old);
|
||||
assertAll("Run := " + fail,
|
||||
() -> assertTrue(fail.stdOut().isEmpty()),
|
||||
() -> assertLinesMatch(
|
||||
"""
|
||||
%s:3: error: ';' expected
|
||||
Helper.run()
|
||||
^
|
||||
1 error
|
||||
""".formatted(old).lines(), fail.stdErr().lines()),
|
||||
() -> assertNotNull(fail.exception()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void classesDeclaredInSameFileArePreferredToClassesInOtherFiles(@TempDir Path base) throws Exception {
|
||||
var prog = Files.writeString(base.resolve("Prog.java"),
|
||||
"""
|
||||
class Helper {
|
||||
static void run() {
|
||||
System.out.println("Same file.");
|
||||
}
|
||||
}
|
||||
public class Prog {
|
||||
public static void main(String... args) {
|
||||
Helper.run();
|
||||
}
|
||||
}
|
||||
""");
|
||||
Files.writeString(base.resolve("Helper.java"),
|
||||
"""
|
||||
class Helper {
|
||||
static void run() {
|
||||
System.out.println("Other file.");
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
var run = Run.of(prog);
|
||||
assertAll("Run := " + run,
|
||||
() -> assertLinesMatch(
|
||||
"""
|
||||
Same file.
|
||||
""".lines(), run.stdOut().lines()),
|
||||
() -> assertTrue(run.stdErr().isEmpty()),
|
||||
() -> assertNull(run.exception()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void duplicateDeclarationOfClassFails(@TempDir Path base) throws Exception {
|
||||
var prog = Files.writeString(base.resolve("Prog.java"),
|
||||
"""
|
||||
class Prog {
|
||||
public static void main(String... args) {
|
||||
Helper.run();
|
||||
Aux.cleanup();
|
||||
}
|
||||
}
|
||||
class Aux {
|
||||
static void cleanup() {}
|
||||
}
|
||||
""");
|
||||
var helper = Files.writeString(base.resolve("Helper.java"),
|
||||
"""
|
||||
class Helper {
|
||||
static void run() {}
|
||||
}
|
||||
class Aux {
|
||||
static void cleanup() {}
|
||||
}
|
||||
""");
|
||||
|
||||
|
||||
var fail = Run.of(prog);
|
||||
assertAll("Run := " + fail,
|
||||
() -> assertTrue(fail.stdOut().isEmpty()),
|
||||
() -> assertLinesMatch(
|
||||
"""
|
||||
%s:4: error: duplicate class: Aux
|
||||
class Aux {
|
||||
^
|
||||
1 error
|
||||
""".formatted(helper).lines(), fail.stdErr().lines()),
|
||||
() -> assertNotNull(fail.exception()));
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8304400
|
||||
* @summary Test source root directory computation
|
||||
* @modules jdk.compiler/com.sun.tools.javac.launcher
|
||||
* @run junit ProgramDescriptorTests
|
||||
*/
|
||||
|
||||
import com.sun.tools.javac.launcher.ProgramDescriptor;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ProgramDescriptorTests {
|
||||
@ParameterizedTest
|
||||
@CsvSource(textBlock =
|
||||
"""
|
||||
'/', '/Program.java', ''
|
||||
'/', '/a/Program.java', 'a',
|
||||
'/', '/a/b/Program.java', 'a.b',
|
||||
'/', '/a/b/c/Program.java', 'a.b.c'
|
||||
|
||||
'/a', '/a/b/c/Program.java', 'b.c'
|
||||
'/a/b', '/a/b/c/Program.java', 'c'
|
||||
'/a/b/c', '/a/b/c/Program.java', ''
|
||||
""")
|
||||
@DisabledOnOs(OS.WINDOWS)
|
||||
void checkComputeSourceRootPath(Path expected, Path program, String packageName) {
|
||||
check(expected, program, packageName);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource(textBlock =
|
||||
"""
|
||||
'C:\\', 'C:\\Program.java', ''
|
||||
'C:\\', 'C:\\a\\Program.java', 'a',
|
||||
'C:\\', 'C:\\a\\b\\Program.java', 'a.b',
|
||||
'C:\\', 'C:\\a\\b\\c\\Program.java', 'a.b.c'
|
||||
|
||||
'C:\\a', 'C:\\a\\b\\c\\Program.java', 'b.c'
|
||||
'C:\\a\\b', 'C:\\a\\b\\c\\Program.java', 'c'
|
||||
'C:\\a\\b\\c', 'C:\\a\\b\\c\\Program.java', ''
|
||||
""")
|
||||
@EnabledOnOs(OS.WINDOWS)
|
||||
void checkComputeSourceRootPathOnWindows(Path expected, Path program, String packageName) {
|
||||
check(expected, program, packageName);
|
||||
}
|
||||
|
||||
private void check(Path expectedRoot, Path programPath, String packageName) {
|
||||
assertTrue(expectedRoot.isAbsolute(), "Expected path not absolute: " + expectedRoot);
|
||||
assertTrue(programPath.isAbsolute(), "Program path not absolute: " + programPath);
|
||||
|
||||
var actual = ProgramDescriptor.computeSourceRootPath(programPath, packageName);
|
||||
assertEquals(expectedRoot, actual);
|
||||
}
|
||||
}
|
63
test/langtools/tools/javac/launcher/Run.java
Normal file
63
test/langtools/tools/javac/launcher/Run.java
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2023, 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.
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.sun.tools.javac.launcher.SourceLauncher;
|
||||
import com.sun.tools.javac.launcher.Result;
|
||||
|
||||
record Run(String stdOut, String stdErr, Throwable exception, Result result) {
|
||||
static Run of(Path file) {
|
||||
return Run.of(file, List.of(), List.of("1", "2", "3"));
|
||||
}
|
||||
|
||||
static Run of(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)) {
|
||||
var launcher = new SourceLauncher(err);
|
||||
var result = launcher.run(runtimeArgs.toArray(String[]::new), args.toArray(String[]::new));
|
||||
return new Run(baos.toString(), sw.toString(), null, result);
|
||||
} catch (Throwable throwable) {
|
||||
return new Run(baos.toString(), sw.toString(), throwable, null);
|
||||
}
|
||||
} finally {
|
||||
System.setOut(prev);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 2023, 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
|
||||
@ -58,13 +58,13 @@ import java.util.Properties;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.sun.tools.javac.launcher.Main;
|
||||
import com.sun.tools.javac.launcher.SourceLauncher;
|
||||
import com.sun.tools.javac.launcher.Fault;
|
||||
|
||||
import toolbox.JavaTask;
|
||||
import toolbox.JavacTask;
|
||||
import toolbox.Task;
|
||||
import toolbox.TestRunner;
|
||||
import toolbox.TestRunner.Test;
|
||||
import toolbox.ToolBox;
|
||||
|
||||
import static jdk.internal.module.ClassFileConstants.WARN_INCUBATING;
|
||||
@ -293,12 +293,20 @@ public class SourceLauncherTest extends TestRunner {
|
||||
|
||||
@Test
|
||||
public void testNoClass(Path base) throws IOException {
|
||||
Files.createDirectories(base);
|
||||
Path file = base.resolve("NoClass.java");
|
||||
var path = Files.createDirectories(base.resolve("p"));
|
||||
Path file = path.resolve("NoClass.java");
|
||||
Files.write(file, List.of("package p;"));
|
||||
testError(file, "", "error: no class declared in source file");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMismatchOfPathAndPackage(Path base) throws IOException {
|
||||
Files.createDirectories(base);
|
||||
Path file = base.resolve("MismatchOfPathAndPackage.java");
|
||||
Files.write(file, List.of("package p;"));
|
||||
testError(file, "", "error: end of path to source file does not match its package name p: " + file);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadClass(Path base) throws IOException {
|
||||
Path src1 = base.resolve("src1");
|
||||
@ -619,7 +627,7 @@ public class SourceLauncherTest extends TestRunner {
|
||||
public void testNoOptionsWarnings(Path base) throws IOException {
|
||||
tb.writeJavaFiles(base, "public class Main { public static void main(String... args) {}}");
|
||||
String log = new JavaTask(tb)
|
||||
.vmOptions("--source", "8")
|
||||
.vmOptions("--source", "21")
|
||||
.className(base.resolve("Main.java").toString())
|
||||
.run(Task.Expect.SUCCESS)
|
||||
.getOutput(Task.OutputKind.STDERR);
|
||||
@ -736,7 +744,7 @@ public class SourceLauncherTest extends TestRunner {
|
||||
System.setOut(out);
|
||||
StringWriter sw = new StringWriter();
|
||||
try (PrintWriter err = new PrintWriter(sw, true)) {
|
||||
Main m = new Main(err);
|
||||
SourceLauncher m = new SourceLauncher(err);
|
||||
m.run(toArray(runtimeArgs), toArray(args));
|
||||
return new Result(baos.toString(), sw.toString(), null);
|
||||
} catch (Throwable t) {
|
||||
@ -793,10 +801,10 @@ public class SourceLauncherTest extends TestRunner {
|
||||
expect = expect.replace("\n", tb.lineSeparator);
|
||||
out.println(name + ": " + found);
|
||||
if (found == null) {
|
||||
error("No exception thrown; expected Main.Fault");
|
||||
error("No exception thrown; expected Fault");
|
||||
} else {
|
||||
if (!(found instanceof Main.Fault)) {
|
||||
error("Unexpected exception; expected Main.Fault");
|
||||
if (!(found instanceof Fault)) {
|
||||
error("Unexpected exception; expected Fault");
|
||||
}
|
||||
if (!(found.getMessage().equals(expect))) {
|
||||
error("Unexpected detail message; expected: " + expect);
|
||||
@ -828,15 +836,5 @@ public class SourceLauncherTest extends TestRunner {
|
||||
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;
|
||||
}
|
||||
}
|
||||
record Result(String stdOut, String stdErr, Throwable exception) {}
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
/*
|
||||
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 2023, 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.
|
||||
* 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
|
@ -95,6 +95,7 @@ public class ListModuleDeps {
|
||||
"java.base/jdk.internal.javac",
|
||||
"java.base/jdk.internal.jmod",
|
||||
"java.base/jdk.internal.misc",
|
||||
"java.base/jdk.internal.module",
|
||||
"java.base/sun.reflect.annotation",
|
||||
"java.compiler",
|
||||
"jdk.internal.opt/jdk.internal.opt",
|
||||
|
Loading…
x
Reference in New Issue
Block a user