/*
* 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.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.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 java.util.stream.Collectors;
import java.util.stream.Stream;
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.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.
/**
* 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.
/**
* 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 --source
option is used, or when the first argument that is not part of a runtime option ends in .java
.
The first entry in the 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 System.exit
with a non-zero return code.
Params: - args – the arguments
Throws: - Throwable – if the main method throws an exception
/**
* Compiles a source file, and executes the main method it contains.
*
* <p>This is normally invoked from the Java launcher, either when
* the {@code --source} option is used, or when the first argument
* that is not part of a runtime option ends in {@code .java}.
*
* <p>The first entry in the {@code args} array is the source file
* to be compiled and run; all subsequent entries are passed as
* arguments to the main method of the first class found in the file.
*
* <p>If any problem occurs before executing the main class, it will
* be reported to the standard error stream, and the the JVM will be
* terminated by calling {@code System.exit} with a non-zero return code.
*
* @param args the arguments
* @throws Throwable if the main method throws an exception
*/
public static void main(String... args) throws Throwable {
try {
new Main(System.err).run(VM.getRuntimeArguments(), args);
} catch (Fault f) {
System.err.println(f.getMessage());
System.exit(1);
} catch (InvocationTargetException e) {
// leave VM to handle the stacktrace, in the standard manner
throw e.getTargetException();
}
}
Stream for reporting errors, such as compilation errors. /** 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.
Params: - out – the stream
/**
* 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.
Params: - out – the stream
/**
* 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 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 javac
are obtained by filtering the runtime arguments.
If the main method throws an exception, it will be propagated in an 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.
Params: - runtimeArgs – the runtime arguments
- args – the arguments
Throws: - Fault – if a problem is detected before the main method can be executed
- InvocationTargetException – if the main method throws an exception
/**
* 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[] appArgs = Arrays.copyOfRange(args, 1, args.length);
execute(mainClassName, appArgs, context);
}
Returns the path for the filename found in the first of an array of arguments.
Params: - args – the array
Throws: - Fault – if there is a problem determining the path, or if the file does not exist
Returns: the path, as given in the array of args
/**
* 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 #!
. If it is not a Java source file, and if the first two bytes are #!
, 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 default platform encoding
.
Params: - file – the file
Throws: - Fault – if an error occurs while reading the file
Returns: a file object containing the content of the 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 javac
. Generally, the relevant options are those for setting paths and for configuring the module system. Params: - runtimeArgs – the runtime arguments
Returns: the subset of the runtime arguments
/**
* Returns the subset of the runtime arguments that are relevant to {@code javac}.
* Generally, the relevant options are those for setting paths and for configuring the
* module system.
*
* @param runtimeArgs the runtime arguments
* @return the subset of the runtime arguments
**/
private List<String> getJavacOpts(String... runtimeArgs) throws Fault {
List<String> javacOpts = new ArrayList<>();
String sourceOpt = System.getProperty("jdk.internal.javac.source");
if (sourceOpt != null) {
Source source = Source.lookup(sourceOpt);
if (source == null) {
throw new Fault(Errors.InvalidValueForSource(sourceOpt));
}
javacOpts.addAll(List.of("--release", sourceOpt));
}
for (int i = 0; i < runtimeArgs.length; i++) {
String arg = runtimeArgs[i];
String opt = arg, value = null;
if (arg.startsWith("--")) {
int eq = arg.indexOf('=');
if (eq > 0) {
opt = arg.substring(0, eq);
value = arg.substring(eq + 1);
}
}
switch (opt) {
// The following options all expect a value, either in the following
// position, or after '=', for options beginning "--".
case "--class-path": case "-classpath": case "-cp":
case "--module-path": case "-p":
case "--add-exports":
case "--add-modules":
case "--limit-modules":
case "--patch-module":
case "--upgrade-module-path":
if (value == null) {
if (i== runtimeArgs.length - 1) {
// should not happen when invoked from launcher
throw new Fault(Errors.NoValueForOption(opt));
}
value = runtimeArgs[++i];
}
if (opt.equals("--add-modules") && value.equals("ALL-DEFAULT")) {
// this option is only supported at run time;
// it is not required or supported at compile time
break;
}
javacOpts.add(opt);
javacOpts.add(value);
break;
case "--enable-preview":
javacOpts.add(opt);
if (sourceOpt == null) {
throw new Fault(Errors.EnablePreviewRequiresSource);
}
break;
default:
// ignore all other runtime args
}
}
// add implicit options
javacOpts.add("-proc:none");
return javacOpts;
}
Compiles a source file, placing the class files in a map in memory.
Any messages generated during compilation will be written to the stream
provided when this object was created.
Params: - file – the source file
- javacOpts – compilation options for
javac
- context – the context for the compilation
Throws: - Fault – if any compilation errors occur, or if no class was found
Returns: the name of the first class found in the source file
/**
* Compiles a source file, placing the class files in a map in memory.
* Any messages generated during compilation will be written to the stream
* provided when this object was created.
*
* @param file the source file
* @param javacOpts compilation options for {@code javac}
* @param context the context for the compilation
* @return the name of the first class found in the source file
* @throws Fault if any compilation errors occur, or if no class was found
*/
private String compile(Path file, List<String> javacOpts, Context context) throws Fault {
JavaFileObject fo = readFile(file);
JavacTool javaCompiler = JavacTool.create();
StandardJavaFileManager stdFileMgr = javaCompiler.getStandardFileManager(null, null, null);
try {
stdFileMgr.setLocation(StandardLocation.SOURCE_PATH, Collections.emptyList());
} catch (IOException e) {
throw new java.lang.Error("unexpected exception from file manager", e);
}
JavaFileManager fm = context.getFileManager(stdFileMgr);
JavacTask t = javaCompiler.getTask(out, fm, null, javacOpts, null, List.of(fo));
MainClassListener l = new MainClassListener(t);
Boolean ok = t.call();
if (!ok) {
throw new Fault(Errors.CompilationFailed);
}
if (l.mainClass == null) {
throw new Fault(Errors.NoClass);
}
String mainClassName = l.mainClass.getQualifiedName().toString();
return mainClassName;
}
Invokes the main
method of a specified class, using a class loader that will load recently compiled classes from memory. Params: - mainClassName – the class to be executed
- appArgs – the arguments for the
main
method - context – the context for the class to be executed
Throws: - Fault – if there is a problem finding or invoking the
main
method - InvocationTargetException – if the
main
method throws an exception
/**
* Invokes the {@code main} method of a specified class, using a class loader that
* will load recently compiled classes from memory.
*
* @param mainClassName the class to be executed
* @param appArgs the arguments for the {@code main} method
* @param context the context for the class to be executed
* @throws Fault if there is a problem finding or invoking the {@code main} method
* @throws InvocationTargetException if the {@code main} method throws an exception
*/
private void execute(String mainClassName, String[] appArgs, Context context)
throws Fault, InvocationTargetException {
System.setProperty("jdk.launcher.sourcefile", context.file.toString());
ClassLoader cl = context.getClassLoader(ClassLoader.getSystemClassLoader());
try {
Class<?> appClass = Class.forName(mainClassName, true, cl);
Method main = appClass.getDeclaredMethod("main", String[].class);
int PUBLIC_STATIC = Modifier.PUBLIC | Modifier.STATIC;
if ((main.getModifiers() & PUBLIC_STATIC) != PUBLIC_STATIC) {
throw new Fault(Errors.MainNotPublicStatic);
}
if (!main.getReturnType().equals(void.class)) {
throw new Fault(Errors.MainNotVoid);
}
main.setAccessible(true);
main.invoke(0, (Object) appArgs);
} catch (ClassNotFoundException e) {
throw new Fault(Errors.CantFindClass(mainClassName));
} catch (NoSuchMethodException e) {
throw new Fault(Errors.CantFindMainMethod(mainClassName));
} catch (IllegalAccessException e) {
throw new Fault(Errors.CantAccessMainMethod(mainClassName));
} catch (InvocationTargetException e) {
// remove stack frames for source launcher
int invocationFrames = e.getStackTrace().length;
Throwable target = e.getTargetException();
StackTraceElement[] targetTrace = target.getStackTrace();
target.setStackTrace(Arrays.copyOfRange(targetTrace, 0, targetTrace.length - invocationFrames));
throw e;
}
}
private static final String bundleName = "com.sun.tools.javac.resources.launcher";
private ResourceBundle resourceBundle = null;
private String errorPrefix;
Returns a localized string from a resource bundle.
Params: - error – the error for which to get the localized text
Returns: the localized string
/**
* 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.
/**
* A listener to detect the first class found in a compilation.
*/
static class MainClassListener implements TaskListener {
TypeElement mainClass;
MainClassListener(JavacTask t) {
t.addTaskListener(this);
}
@Override
public void started(TaskEvent ev) {
if (ev.getKind() == TaskEvent.Kind.ANALYZE && mainClass == null) {
TypeElement te = ev.getTypeElement();
if (te.getNestingKind() == NestingKind.TOP_LEVEL) {
mainClass = te;
}
}
}
}
An object to encapulate the set of in-memory classes, such that
they can be written by a file manager and subsequently used by
a class loader.
/**
* An object to encapulate the set of in-memory classes, such that
* they can be written by a file manager and subsequently used by
* a class loader.
*/
private static class Context {
private 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.
Class files (of kind CLASS
written to StandardLocation.CLASS_OUTPUT
will be written to an in-memory cache. All other file manager operations will be delegated to a specified file manager.
/**
* 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 MemoryFileManager
. 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.)
/**
* 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 binary name
. /**
* 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.
/**
* 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. loadClass(String name)
calls this method, and so will have the same behavior. Params: - name – the name of the class to load
- resolve – whether or not to resolve the class
Throws: - ClassNotFoundException – if the class is not found
Returns: the class
/**
* 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. getResourceAsStream(String name)
calls this method, and so will have the same behavior. Params: - name – the name of the resource
Returns: a URL for the resource, or null if not found
/**
* 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.
Params: - name – the name of the resource
Returns: an enumeration of the resources in this loader and in the application class loader
/**
* 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 {
return new URL(PROTOCOL, null, -1, name, handler);
} 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.
Params: - name – the resource name
Returns: the binary name
/**
* 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.
/**
* 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.
/**
* 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";
}
}
}
}