package com.oracle.svm.hosted;
import static com.oracle.svm.core.util.VMError.shouldNotReachHere;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.channels.ClosedByInterruptException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.util.ClasspathUtils;
import com.oracle.svm.core.util.InterruptImageBuilding;
import com.oracle.svm.core.util.UserError;
import com.oracle.svm.core.util.VMError;
public abstract class AbstractNativeImageClassLoaderSupport {
public static final String PROPERTY_IMAGEINCLUDEBUILTINMODULES = "substratevm.ImageIncludeBuiltinModules";
final List<Path> imagecp;
private final List<Path> buildcp;
protected final URLClassLoader classPathClassLoader;
protected AbstractNativeImageClassLoaderSupport(ClassLoader defaultSystemClassLoader, String[] classpath) {
classPathClassLoader = new URLClassLoader(Util.verifyClassPathAndConvertToURLs(classpath), defaultSystemClassLoader);
imagecp = Collections.unmodifiableList(Arrays.stream(classPathClassLoader.getURLs()).map(Util::urlToPath).collect(Collectors.toList()));
buildcp = Collections.unmodifiableList(Arrays.stream(System.getProperty("java.class.path").split(File.pathSeparator)).map(Paths::get).collect(Collectors.toList()));
}
List<Path> classpath() {
return Stream.concat(buildcp.stream(), imagecp.stream()).collect(Collectors.toList());
}
List<Path> applicationClassPath() {
return imagecp;
}
ClassLoader getClassLoader() {
return classPathClassLoader;
}
abstract Class<?> loadClassFromModule(Object module, String className) throws ClassNotFoundException;
abstract List<Path> modulepath();
abstract List<Path> applicationModulePath();
abstract Optional<Object> findModule(String moduleName);
abstract void initAllClasses(ForkJoinPool executor, ImageClassLoader imageClassLoader);
protected static class Util {
static URL[] verifyClassPathAndConvertToURLs(String[] classpath) {
Stream<Path> pathStream = new LinkedHashSet<>(Arrays.asList(classpath)).stream().flatMap(Util::toClassPathEntries);
return pathStream.map(v -> {
try {
return v.toAbsolutePath().toUri().toURL();
} catch (MalformedURLException e) {
throw UserError.abort("Invalid classpath element '%s'. Make sure that all paths provided with '%s' are correct.", v, SubstrateOptions.IMAGE_CLASSPATH_PREFIX);
}
}).toArray(URL[]::new);
}
static Stream<Path> toClassPathEntries(String classPathEntry) {
Path entry = ClasspathUtils.stringToClasspath(classPathEntry);
if (entry.endsWith(ClasspathUtils.cpWildcardSubstitute)) {
try {
return Files.list(entry.getParent()).filter(ClasspathUtils::isJar);
} catch (IOException e) {
return Stream.empty();
}
}
if (Files.isReadable(entry)) {
return Stream.of(entry);
}
return Stream.empty();
}
static Path urlToPath(URL url) {
try {
return Paths.get(url.toURI());
} catch (URISyntaxException e) {
throw VMError.shouldNotReachHere();
}
}
}
protected static class ClassInit {
protected final ForkJoinPool executor;
protected final ImageClassLoader imageClassLoader;
protected final AbstractNativeImageClassLoaderSupport nativeImageClassLoader;
ClassInit(ForkJoinPool executor, ImageClassLoader imageClassLoader, AbstractNativeImageClassLoaderSupport nativeImageClassLoader) {
this.executor = executor;
this.imageClassLoader = imageClassLoader;
this.nativeImageClassLoader = nativeImageClassLoader;
}
protected void init() {
Set<Path> uniquePaths = new TreeSet<>(Comparator.comparing(ClassInit::toRealPath));
uniquePaths.addAll(nativeImageClassLoader.classpath());
uniquePaths.parallelStream().forEach(path -> loadClassesFromPath(path));
}
private static Path toRealPath(Path p) {
try {
return p.toRealPath();
} catch (IOException e) {
throw VMError.shouldNotReachHere("Path.toRealPath failed for " + p, e);
}
}
private static final Set<Path> excludeDirectories = getExcludeDirectories();
private static Set<Path> getExcludeDirectories() {
Path root = Paths.get("/");
return Stream.of("dev", "sys", "proc", "etc", "var", "tmp", "boot", "lost+found")
.map(root::resolve).collect(Collectors.toSet());
}
private void loadClassesFromPath(Path path) {
if (Files.exists(path)) {
if (Files.isRegularFile(path)) {
try {
URI jarURI = new URI("jar:" + path.toAbsolutePath().toUri());
FileSystem probeJarFileSystem;
try {
probeJarFileSystem = FileSystems.newFileSystem(jarURI, Collections.emptyMap());
} catch (UnsupportedOperationException e) {
probeJarFileSystem = null;
}
if (probeJarFileSystem != null) {
try (FileSystem jarFileSystem = probeJarFileSystem) {
loadClassesFromPath(jarFileSystem.getPath("/"), Collections.emptySet());
}
}
} catch (ClosedByInterruptException ignored) {
throw new InterruptImageBuilding();
} catch (IOException | URISyntaxException e) {
throw shouldNotReachHere(e);
}
} else {
loadClassesFromPath(path, excludeDirectories);
}
}
}
protected static final String CLASS_EXTENSION = ".class";
private void loadClassesFromPath(Path root, Set<Path> excludes) {
FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
private final char fileSystemSeparatorChar = root.getFileSystem().getSeparator().charAt(0);
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
if (excludes.contains(dir)) {
return FileVisitResult.SKIP_SUBTREE;
}
return super.preVisitDirectory(dir, attrs);
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
assert !excludes.contains(file.getParent()) : "Visiting file '" + file + "' with excluded parent directory";
String fileName = root.relativize(file).toString();
if (fileName.endsWith(CLASS_EXTENSION)) {
executor.execute(() -> handleClassFileName(unversionedFileName(fileName), fileSystemSeparatorChar));
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
return FileVisitResult.CONTINUE;
}
private String unversionedFileName(String fileName) {
final String versionedPrefix = "META-INF/versions/";
final String versionedSuffix = "/";
String result = fileName;
if (fileName.startsWith(versionedPrefix)) {
final int versionedSuffixIndex = fileName.indexOf(versionedSuffix, versionedPrefix.length());
if (versionedSuffixIndex >= 0) {
result = fileName.substring(versionedSuffixIndex + versionedSuffix.length());
}
}
return classFileWithoutSuffix(result);
}
};
try {
Files.walkFileTree(root, visitor);
} catch (IOException ex) {
throw shouldNotReachHere(ex);
}
}
static String classFileWithoutSuffix(String result) {
return result.substring(0, result.length() - CLASS_EXTENSION.length());
}
protected void handleClassFileName(String strippedClassFileName, char fileSystemSeparatorChar) {
if (strippedClassFileName.equals("module-info")) {
return;
}
String className = strippedClassFileName.replace(fileSystemSeparatorChar, '.');
Class<?> clazz = null;
try {
clazz = imageClassLoader.forName(className);
} catch (Throwable t) {
ImageClassLoader.handleClassLoadingError(t);
}
if (clazz != null) {
imageClassLoader.handleClass(clazz);
}
}
}
}