Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FXSampler: Scan Class path as well as Module Path #1314

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
213 changes: 183 additions & 30 deletions fxsampler/src/main/java/fxsampler/util/SampleScanner.java
@@ -1,5 +1,5 @@
/**
* Copyright (c) 2013, 2020, ControlsFX
* Copyright (c) 2013, 2021, ControlsFX
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
Expand All @@ -26,24 +26,22 @@
*/
package fxsampler.util;

import fxsampler.FXSamplerProject;
import fxsampler.Sample;
import fxsampler.model.EmptySample;
import fxsampler.model.Project;

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.module.ModuleReader;
import java.lang.module.ResolvedModule;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;

import fxsampler.FXSamplerProject;
import fxsampler.Sample;
import fxsampler.model.EmptySample;
import fxsampler.model.Project;
import java.net.URL;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;

/**
* All the code related to classpath scanning, etc for samples.
Expand Down Expand Up @@ -127,15 +125,37 @@ public Map<String, Project> discoverSamples() {
}
}
return projectsMap;
}
}

private Class<?>[] loadFromPathScanning() throws IOException {
try {
Set<Class<?>> results = new HashSet<>();

Class<?>[] path = loadFromClassPathScanning();
System.out.println( "Classpath\n--------------------------------" );
System.out.println( Arrays.toString(path).replace(",", "\n"));
results.addAll( Arrays.asList(path));

path = loadFromModulePathScanning();
System.out.println( "Module Path\n--------------------------------" );
System.out.println( Arrays.toString(path).replace(",", ",\n"));
results.addAll( Arrays.asList(path));

return results.toArray( new Class<?>[0]);

} catch (Exception e) {
e.printStackTrace();
}
return new Class<?>[0];
}

/**
* Scans all classes.
* Scans all classes from the module path.
*
* @return The classes
* @throws IOException
*/
private Class<?>[] loadFromPathScanning() throws IOException {
private Class<?>[] loadFromModulePathScanning() throws IOException {

final Set<Class<?>> classes = new LinkedHashSet<>();
// scan the module-path
Expand All @@ -157,13 +177,158 @@ private Class<?>[] loadFromPathScanning() throws IOException {
return classes.toArray(new Class[classes.size()]);
}


/**
* Return true if the given module name is a system module. There can be
* system modules in layers above the boot layer.
*/
private static boolean isSystemModule(final String moduleName) {
return moduleName.startsWith("java.")
|| moduleName.startsWith("javax.")
|| moduleName.startsWith("javafx.")
|| moduleName.startsWith("jdk.")
|| moduleName.startsWith("oracle.");
}


/**
* Scans all classes on the classpath.
*
* @return The classes
* @throws ClassNotFoundException
* @throws IOException
*/
private Class<?>[] loadFromClassPathScanning() throws ClassNotFoundException, IOException {
final List<File> dirs = new ArrayList<>();
final List<File> jars = new ArrayList<>();

// scan the classpath
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String path = "";
Enumeration<URL> resources = classLoader.getResources(path);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();

if (url.toExternalForm().contains("/jre/")) continue;

// Only "file" and "jar" URLs are recognized, other schemes will be ignored.
String protocol = url.getProtocol().toLowerCase();
if ("file".equals(protocol)) {
dirs.add(new File(url.getFile()));
} else if ("jar".equals(protocol)) {
String fileName = new URL(url.getFile()).getFile();

// JAR URL specs must contain the string "!/" which separates the name
// of the JAR file from the path of the resource contained in it, even
// if the path is empty.
int sep = fileName.indexOf("!/");
if (sep > 0) {
jars.add(new File(fileName.substring(0, sep)));
}
}
}

// and also scan the current working directory
final Path workingDirectory = new File("").toPath();
scanPath(workingDirectory, dirs, jars);

// process directories first, then jars, so that classes take precedence
// over built jars (it makes rapid development easier in the IDE)
final Set<Class<?>> classes = new LinkedHashSet<>();
for (File directory : dirs) {
classes.addAll(findClassesInDirectory(directory));
}
for (File jar : jars) {
String fullPath = jar.getAbsolutePath();
if (fullPath.endsWith("jfxrt.jar")) continue;
classes.addAll(findClassesInJar(new File(fullPath)));
}

return classes.toArray(new Class[classes.size()]);
}

private void scanPath(Path workingDirectory, final List<File> dirs, final List<File> jars) throws IOException {
Files.walkFileTree(workingDirectory, new SimpleFileVisitor<Path>() {
@Override public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
final File file = path.toFile();
final String fullPath = file.getAbsolutePath();
final String name = file.toString();

if (fullPath.endsWith("jfxrt.jar") || name.contains("jre")) {
return FileVisitResult.CONTINUE;
}

if (file.isDirectory()) {
dirs.add(file);
} else if (name.toLowerCase().endsWith(".jar")) {
jars.add(file);
}
return FileVisitResult.CONTINUE;
}

@Override public FileVisitResult visitFileFailed(Path file, IOException ex) {
System.err.println(ex + " Skipping...");
return FileVisitResult.CONTINUE;
}
});
}

private List<Class<?>> findClassesInDirectory(File directory) throws IOException {
List<Class<?>> classes = new ArrayList<>();
if (!directory.exists()) {
System.out.println("Directory does not exist: " + directory.getAbsolutePath());
return classes;
}

processPath(directory.toPath(), classes);
return classes;
}

private List<Class<?>> findClassesInJar(File jarFile) throws IOException, ClassNotFoundException {
List<Class<?>> classes = new ArrayList<>();
if (!jarFile.exists()) {
System.out.println("Jar file does not exist here: " + jarFile.getAbsolutePath());
return classes;
}

FileSystem jarFileSystem = FileSystems.newFileSystem(jarFile.toPath(), null);
processPath(jarFileSystem.getPath("/"), classes);
return classes;
}

private void processPath(Path path, final List<Class<?>> classes) throws IOException {
final String root = path.toString();

Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
@Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
String name = file.toString();
if (name.endsWith(".class") && ! ILLEGAL_CLASS_NAMES.contains(name)) {

// remove root path to make class name correct in all cases
name = name.substring(root.length());

Class<?> clazz = processClassName(name);
if (clazz != null) {
classes.add(clazz);
}
}
return FileVisitResult.CONTINUE;
}

@Override public FileVisitResult visitFileFailed(Path file, IOException ex) {
System.err.println(ex + " Skipping...");
return FileVisitResult.CONTINUE;
}
});
}

private Class<?> processClassName(final String name) {
String className = name.replace("\\", ".");
className = className.replace("/", ".");

// some cleanup code
if (className.contains("$")) {
// we don't care about samples as inner classes, so
// we don't care about samples as inner classes, so
// we jump out
return null;
}
Expand All @@ -189,16 +354,4 @@ private Class<?> processClassName(final String name) {
}
return clazz;
}

/**
* Return true if the given module name is a system module. There can be
* system modules in layers above the boot layer.
*/
private static boolean isSystemModule(final String moduleName) {
return moduleName.startsWith("java.")
|| moduleName.startsWith("javax.")
|| moduleName.startsWith("javafx.")
|| moduleName.startsWith("jdk.")
|| moduleName.startsWith("oracle.");
}
}