/* * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.javac.launcher; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.PrintWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URI; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.MissingResourceException; import java.util.ResourceBundle; import javax.lang.model.element.NestingKind; import javax.lang.model.element.TypeElement; import javax.tools.FileObject; import javax.tools.ForwardingJavaFileManager; import javax.tools.JavaFileManager; import javax.tools.JavaFileManager.Location; import javax.tools.JavaFileObject; import javax.tools.SimpleJavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.StandardLocation; import com.sun.source.util.JavacTask; import com.sun.source.util.TaskEvent; import com.sun.source.util.TaskListener; import com.sun.tools.javac.api.JavacTool; import com.sun.tools.javac.code.Source; import com.sun.tools.javac.resources.LauncherProperties.Errors; import com.sun.tools.javac.util.JCDiagnostic.Error; import jdk.internal.misc.VM; import static javax.tools.JavaFileObject.Kind.SOURCE; /** * Compiles a source file, and executes the main method it contains. * *
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.
*/ 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. * *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}. * *
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. * *
If any problem occurs before executing the main class, it will * be reported to the standard error stream, and the the JVM will be * terminated by calling {@code System.exit} with a non-zero return code. * * @param args the arguments * @throws Throwable if the main method throws an exception */ public static void main(String... args) throws Throwable { try { new Main(System.err).run(VM.getRuntimeArguments(), args); } catch (Fault f) { System.err.println(f.getMessage()); System.exit(1); } catch (InvocationTargetException e) { // leave VM to handle the stacktrace, in the standard manner throw e.getTargetException(); } } /** Stream for reporting errors, such as compilation errors. */ private PrintWriter out; /** * Creates an instance of this class, providing a stream to which to report * any errors. * * @param out the stream */ public Main(PrintStream out) { this(new PrintWriter(new OutputStreamWriter(out), true)); } /** * Creates an instance of this class, providing a stream to which to report * any errors. * * @param out the stream */ public Main(PrintWriter out) { this.out = out; } /** * Compiles a source file, and executes the main method it contains. * *
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. * *
Options for {@code javac} are obtained by filtering the runtime arguments. * *
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);
Map 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))) {
in.mark(2);
int magic = (in.read() << 8) + in.read();
boolean shebang = magic == (('#' << 8) + '!');
if (!shebang) {
in.reset();
}
try (BufferedReader r = new BufferedReader(new InputStreamReader(in, Charset.defaultCharset()))) {
StringBuilder sb = new StringBuilder();
if (shebang) {
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) {
return (kind == JavaFileObject.Kind.SOURCE)
&& !simpleName.equals("package-info");
}
@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 Class files (of kind {@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.
*/
static class MemoryFileManager extends ForwardingJavaFileManager The classloader uses the standard parent-delegation model, just providing
* {@code findClass} to find classes in the in-memory cache.
*/
static class MemoryClassLoader extends ClassLoader {
private final Map