Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion jjava-kernel/pom.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
Expand Down Expand Up @@ -30,4 +31,17 @@
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>--add-opens jdk.jshell/jdk.jshell=ALL-UNNAMED</argLine>
<reuseForks>false</reuseForks>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ protected JavaKernel(
MagicParser magicParser,
MagicsRegistry magicsRegistry,
ExtensionLoader extensionLoader,
String extraClasspath,
boolean extensionsEnabled,
StringStyler errorStyler,
JShell jShell,
Expand All @@ -101,6 +102,9 @@ protected JavaKernel(

if (extensionsEnabled) {
this.extensionLoader.loadFromDefaultClasspath().forEach(e -> e.install(this));
if (extraClasspath != null) {
this.extensionLoader.loadFromClasspath(extraClasspath).forEach(e -> e.install(this));
}
}
}

Expand Down Expand Up @@ -383,6 +387,7 @@ public JavaKernel build() {
buildMagicParser(magicTranspiler),
buildMagicsRegistry(),
buildExtensionLoader(),
buildExtraClasspath(),
buildExtensionsEnabled(),
buildErrorStyler(),
jShell,
Expand All @@ -396,5 +401,9 @@ protected List<HelpLink> buildHelpLinks() {
new HelpLink("JJava homepage", "https://github.com/dflib/jjava")
);
}

private String buildExtraClasspath() {
return extraClasspath;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.dflib.jjava.kernel;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertNotNull;

class ExtensionLoadingBuiltInTest {

@Test
void loadsExtensionsFromInitialClasspath() {
JavaKernel.builder().name("TestKernel").build();
assertNotNull(JavaNotebookStatics.kernel(), "Built-in extension should be installed from initial classpath");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.dflib.jjava.kernel;

import org.dflib.jjava.jupyter.kernel.util.PathsHandler;
import org.junit.jupiter.api.Test;

import java.nio.file.Path;

import static org.junit.jupiter.api.Assertions.assertEquals;

class ExtensionLoadingExtraClasspathTest {

@Test
void loadsExtensionsFromExtraClasspath() throws Exception {
Path jar = TestJarFactory.buildJar(
"java/",
"java/org/dflib/jjava/kernel/test/TestExtension.java",
"java/META-INF/services/org.dflib.jjava.jupyter.Extension"
);

String extraClasspath = PathsHandler.joinPaths(PathsHandler.resolveGlobs(jar.toAbsolutePath().toString()));

String extensionClassName = "org.dflib.jjava.kernel.test.TestExtension";
String installationsProperty = extensionInstallationsProperty(extensionClassName);
System.clearProperty(installationsProperty);

JavaKernel kernel = JavaKernel.builder().name("TestKernel").extraClasspath(extraClasspath).build();
assertEquals("1", System.getProperty(installationsProperty));

kernel.addToClasspath(extraClasspath);
assertEquals("1", System.getProperty(installationsProperty));
}

private static String extensionInstallationsProperty(String className) {
return "ext.installs:" + className;
}
}
119 changes: 119 additions & 0 deletions jjava-kernel/src/test/java/org/dflib/jjava/kernel/TestJarFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package org.dflib.jjava.kernel;

import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.stream.Collectors;
import java.util.stream.Stream;

final class TestJarFactory {

private static final Map<Set<String>, Path> cache = new HashMap<>();

private TestJarFactory() {
}

static synchronized Path buildJar(String prefix, String... resourcePaths) throws Exception {
Set<String> cacheKey = Set.of(resourcePaths);
if (cache.containsKey(cacheKey)) {
Path cached = cache.get(cacheKey);
if (Files.exists(cached)) {
return cached;
}
}

Path tmpDir = Files.createTempDirectory("jjava-test-jar-");
Path srcDir = tmpDir.resolve("src");
Path classesDir = tmpDir.resolve("classes");
Files.createDirectories(srcDir);
Files.createDirectories(classesDir);

moveResources(prefix, resourcePaths, srcDir, classesDir);
compileJava(srcDir, classesDir);
Path jar = packageJar(tmpDir.resolve("test.jar"), classesDir);

cache.put(cacheKey, jar);
return jar;
}

private static void moveResources(String prefix, String[] resourcePaths, Path srcDir, Path classesDir) throws IOException {
for (String resourcePath : resourcePaths) {
URL url = TestJarFactory.class.getResource("/" + resourcePath);
if (url == null) {
throw new IllegalArgumentException("Resource not found: " + resourcePath);
}

String targetRelativePath = resourcePath;
if (prefix != null && !prefix.isEmpty() && resourcePath.startsWith(prefix)) {
targetRelativePath = resourcePath.substring(prefix.length());
}

Path targetDir = resourcePath.endsWith(".java") ? srcDir : classesDir;
Path targetPath = targetDir.resolve(targetRelativePath);
Files.createDirectories(targetPath.getParent());
try (InputStream in = url.openStream()) {
Files.copy(in, targetPath, StandardCopyOption.REPLACE_EXISTING);
}
}
}

private static void compileJava(Path srcDir, Path classesDir) throws IOException {
List<File> srcFiles;
try (Stream<Path> stream = Files.walk(srcDir)) {
srcFiles = stream.filter(Files::isRegularFile).map(Path::toFile).collect(Collectors.toList());
}
if (srcFiles.isEmpty()) {
return;
}

JavaCompiler compiler = Objects.requireNonNull(ToolProvider.getSystemJavaCompiler());
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
try (StandardJavaFileManager fm = compiler.getStandardFileManager(diagnostics, null, null)) {
fm.setLocation(StandardLocation.CLASS_OUTPUT, List.of(classesDir.toFile()));
Iterable<? extends JavaFileObject> units = fm.getJavaFileObjectsFromFiles(srcFiles);
Boolean ok = compiler.getTask(null, fm, diagnostics, null, null, units).call();
if (!Boolean.TRUE.equals(ok)) {
throw new IllegalStateException("Compilation failed: " + diagnostics.getDiagnostics());
}
}
}

private static Path packageJar(Path jarPath, Path classesDir) throws IOException {
try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(jarPath))) {
writeDirToJar(jos, classesDir, classesDir);
}
return jarPath;
}

private static void writeDirToJar(JarOutputStream jos, Path root, Path dir) throws IOException {
try (Stream<Path> stream = Files.walk(dir)) {
for (Path path : (Iterable<Path>) stream::iterator) {
if (Files.isDirectory(path)) {
continue;
}
String entryName = root.relativize(path).toString().replace('\\', '/');
JarEntry entry = new JarEntry(entryName);
jos.putNextEntry(entry);
Files.copy(path, jos);
jos.closeEntry();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.dflib.jjava.kernel.test.TestExtension
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.dflib.jjava.kernel.test;

import org.dflib.jjava.jupyter.Extension;
import org.dflib.jjava.jupyter.kernel.BaseKernel;

public class TestExtension implements Extension {

@Override
public void install(BaseKernel kernel) {
String key = "ext.installs:" + getClass().getName();
String value = System.getProperty(key, "0");
try {
int count = Integer.parseInt(value);
System.setProperty(key, String.valueOf(count + 1));
} catch (NumberFormatException e) {
System.setProperty(key, "1");
}
}
}