diff --git a/build.gradle b/build.gradle index 5d44b4afc3..01300b87d1 100644 --- a/build.gradle +++ b/build.gradle @@ -43,11 +43,6 @@ dependencies { // FIXME: add test resources. sourceSets { - corecomm { - resources { - srcDirs = ['java/org/contikios/cooja/corecomm'] - } - } data { resources { srcDirs = ['tools/coffee-manager'] @@ -57,10 +52,9 @@ sourceSets { main { java { srcDirs = ['java', 'tools/coffee-manager'] - exclude 'org/contikios/cooja/corecomm/CoreCommTemplate.java' } resources { - srcDirs = [corecomm.resources, data.resources, 'config'] + srcDirs = [data.resources, 'config'] } } } @@ -124,10 +118,7 @@ tasks.register('fullJar', Jar) { run { // Bad Cooja location detected with gradle run, explicitly pass -cooja. doFirst { - args += ['--cooja', "$projectDir", - '--javac', javaToolchains.compilerFor { - languageVersion = JavaLanguageVersion.of(javaVersion) - }.get().executablePath] + args += ['--cooja', "$projectDir"] } // Connect stdin to make the MSPSim CLI work, except when running in CI. if (System.getenv('CI') == null) { diff --git a/java/org/contikios/cooja/Cooja.java b/java/org/contikios/cooja/Cooja.java index 11555f095f..3e5037e833 100644 --- a/java/org/contikios/cooja/Cooja.java +++ b/java/org/contikios/cooja/Cooja.java @@ -1687,7 +1687,7 @@ public static void loadQuickHelp(final Object obj) { */ public record Config(LogbackColors logColors, boolean vis, String externalToolsConfig, String nashornArgs, String logDir, - String contikiPath, String coojaPath, String javac) {} + String contikiPath, String coojaPath) {} public record LogbackColors(String error, String warn, String info, String fallback) {} private record PathIdentifier(String id, String path) {} diff --git a/java/org/contikios/cooja/Main.java b/java/org/contikios/cooja/Main.java index ba5400dfff..9990aa0b83 100644 --- a/java/org/contikios/cooja/Main.java +++ b/java/org/contikios/cooja/Main.java @@ -96,12 +96,6 @@ class Main { @Option(names = "--cooja", paramLabel = "DIR", description = "the Cooja directory") String coojaPath; - /** - * Option for specifying javac path. - */ - @Option(names = "--javac", paramLabel = "FILE", description = "the javac binary") - String javac; - /** * Option for specifying external user config file. */ @@ -301,23 +295,13 @@ public static void main(String[] args) { System.exit(1); } - if (options.mspSimPlatform == null && options.javac == null) { - System.err.println("Missing required option: '--javac=FILE'"); - System.exit(1); - } - - if (options.mspSimPlatform == null && !Files.exists(Path.of(options.javac))) { - System.err.println("Java compiler '" + options.javac + "' does not exist"); - System.exit(1); - } - if (options.mspSimPlatform == null) { // Start Cooja. // Use colors that are good on a dark background and readable on a white background. var colors = new LogbackColors(ANSIConstants.BOLD + "91", "96", ANSIConstants.GREEN_FG, ANSIConstants.DEFAULT_FG); var cfg = new Config(colors, options.gui, options.externalUserConfig, options.nashornArgs, - options.logDir, options.contikiPath, options.coojaPath, options.javac); + options.logDir, options.contikiPath, options.coojaPath); Cooja.go(cfg, simConfigs); } else { // Start MSPSim. var config = new ArgumentManager(options.simulationFiles.toArray(new String[0])); diff --git a/java/org/contikios/cooja/MoteType.java b/java/org/contikios/cooja/MoteType.java index 3907aff68a..dbaec89743 100644 --- a/java/org/contikios/cooja/MoteType.java +++ b/java/org/contikios/cooja/MoteType.java @@ -157,6 +157,12 @@ default long getExecutableAddressOf(File file, int lineNr) { return -1; } + /** Called when the mote type is added to the simulation. */ + default void added() { } + + /** Called when the mote type is removed from the simulation. */ + default void removed() { } + class MoteTypeCreationException extends Exception { private MessageList compilationOutput; diff --git a/java/org/contikios/cooja/Simulation.java b/java/org/contikios/cooja/Simulation.java index 83fd8e023f..421ed0d4c3 100644 --- a/java/org/contikios/cooja/Simulation.java +++ b/java/org/contikios/cooja/Simulation.java @@ -883,6 +883,7 @@ public MoteType[] getMoteTypes() { public void addMoteType(MoteType newMoteType) { Cooja.usedMoteTypeIDs.add(newMoteType.getIdentifier()); moteTypes.add(newMoteType); + newMoteType.added(); moteTypeTriggers.trigger(AddRemove.ADD, newMoteType); } @@ -897,14 +898,10 @@ public void removeMoteType(MoteType type) { removeMote(m); } } - - // FIXME: call type.removed(), so ContikiMoteType can call arena.close() - // to unload the shared library. - // For more details, see "Obtaining a symbol lookup" at: - // https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/foreign/SymbolLookup.html if (moteTypes.remove(type)) { moteTypeTriggers.trigger(AddRemove.REMOVE, type); } + type.removed(); } /** diff --git a/java/org/contikios/cooja/contikimote/ContikiMoteType.java b/java/org/contikios/cooja/contikimote/ContikiMoteType.java index d5ceeced67..aa45e53a24 100644 --- a/java/org/contikios/cooja/contikimote/ContikiMoteType.java +++ b/java/org/contikios/cooja/contikimote/ContikiMoteType.java @@ -31,30 +31,23 @@ import static java.nio.charset.StandardCharsets.UTF_8; -import java.io.BufferedReader; import java.io.File; import java.io.IOException; -import java.io.InputStreamReader; +import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; import java.lang.foreign.SegmentScope; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.contikios.cooja.AbstractionLevelDescription; import org.contikios.cooja.ClassDescription; import org.contikios.cooja.Cooja; -import org.contikios.cooja.CoreComm; import org.contikios.cooja.Mote; import org.contikios.cooja.MoteInterface; import org.contikios.cooja.ProjectConfig; @@ -113,10 +106,9 @@ // Do not bother end-user with warnings about internal Cooja details. @SuppressWarnings("preview") public class ContikiMoteType extends BaseContikiMoteType { - private static final Logger logger = LoggerFactory.getLogger(ContikiMoteType.class); - private static int fileCounter = 1; - + // Shared Arena since MoteTypes are allocated/removed in different threads. + private final Arena arena = Arena.openShared(); /** * Communication stacks in Contiki. */ @@ -247,20 +239,9 @@ public boolean loadMoteFirmware(boolean vis) throws MoteTypeCreationException { if (myCoreComm != null) { throw new MoteTypeCreationException("Core communicator already used: " + myCoreComm.getClass().getName()); } - Path tmpDir; - try { - tmpDir = Files.createTempDirectory("cooja"); - } catch (IOException e) { - logger.warn("Failed to create temp directory:" + e); - return false; - } - tmpDir.toFile().deleteOnExit(); - - // Create, compile, and load the Java wrapper that loads the C library. - // Allocate core communicator class final var firmwareFile = getContikiFirmwareFile(); - myCoreComm = createCoreComm(tmpDir, firmwareFile); + myCoreComm = new CoreComm(arena.scope(), firmwareFile); /* Parse addresses using map file * or output of command specified in external tools settings (e.g. nm -a ) @@ -788,112 +769,8 @@ public boolean setConfigXML(Simulation simulation, return configureAndInit(Cooja.getTopParentContainer(), simulation, Cooja.isVisualized()); } - /** - * Generates new source file by reading default source template and replacing - * the class name field. - * - * @param tempDir Directory for temporary files - * @param className Java class name (without extension) - * @throws MoteTypeCreationException If error occurs - */ - private static void generateLibSourceFile(Path tempDir, String className) throws MoteTypeCreationException { - // Create the temporary directory and ensure it is deleted on exit. - File dir = tempDir.toFile(); - StringBuilder path = new StringBuilder(tempDir.toString()); - // Gradually do mkdir() since mkdirs() makes deleteOnExit() leave the - // directory when Cooja quits. - for (String p : new String[]{"/org", "/contikios", "/cooja", "/corecomm"}) { - path.append(p); - dir = new File(path.toString()); - if (!dir.mkdir()) { - throw new MoteTypeCreationException("Could not create temporary directory: " + dir); - } - dir.deleteOnExit(); - } - Path dst = Path.of(dir + "/" + className + ".java"); - dst.toFile().deleteOnExit(); - - // Instantiate the CoreComm template into the temporary directory. - try (var input = ContikiMoteType.class.getResourceAsStream('/' + "CoreCommTemplate.java"); - var reader = new BufferedReader(new InputStreamReader(Objects.requireNonNull(input), UTF_8)); - var writer = Files.newBufferedWriter(dst, UTF_8)) { - String line; - while ((line = reader.readLine()) != null) { - writer.write(line.replace("CoreCommTemplate", className) + "\n"); - } - } catch (Exception e) { - throw new MoteTypeCreationException("Could not generate corecomm source file: " + className + ".java", e); - } - } - - /** - * Compiles Java class. - * - * @param tempDir Directory for temporary files - * @param className Java class name (without extension) - * @throws MoteTypeCreationException If Java class compilation error occurs - */ - private static void compileSourceFile(Path tempDir, String className) throws MoteTypeCreationException { - String[] cmd = {Cooja.configuration.javac(), - "-cp", System.getProperty("java.class.path"), "--release", String.valueOf(Runtime.version().feature()), - "--enable-preview", - tempDir + "/org/contikios/cooja/corecomm/" + className + ".java" }; - ProcessBuilder pb = new ProcessBuilder(cmd); - Process p; - try { - p = pb.start(); - } catch (IOException e) { - throw new MoteTypeCreationException("Could not start Java compiler: " + cmd[0], e); - } - - // Try to create a message list with support for GUI - will give not UI if headless. - var compilationOutput = MessageContainer.createMessageList(true); - var stdout = compilationOutput.getInputStream(MessageList.NORMAL); - var stderr = compilationOutput.getInputStream(MessageList.ERROR); - try (var outputStream = p.inputReader(UTF_8); - var errorStream = p.errorReader(UTF_8)) { - int b; - while ((b = outputStream.read()) >= 0) { - stdout.write(b); - } - while ((b = errorStream.read()) >= 0) { - stderr.write(b); - } - if (p.waitFor() == 0) { - File classFile = new File(tempDir + "/org/contikios/cooja/corecomm/" + className + ".class"); - classFile.deleteOnExit(); - return; - } - } catch (IOException | InterruptedException e) { - throw new MoteTypeCreationException("Could not compile corecomm source file: " + className + ".java", e, compilationOutput); - } - throw new MoteTypeCreationException("Could not compile corecomm source file: " + className + ".java", compilationOutput); - } - - /** - * Create and return an instance of the core communicator identified by - * className. This core communicator will load the native library libFile. - * - * @param tempDir Directory for temporary files - * @param libFile Native library file - * @return Core Communicator - */ - private static CoreComm createCoreComm(Path tempDir, File libFile) throws MoteTypeCreationException { - // Loading a class might leave residue in the JVM so use a new name for the next call. - final var className = "Lib" + fileCounter++; - generateLibSourceFile(tempDir, className); - compileSourceFile(tempDir, className); - Class extends CoreComm> newCoreCommClass; - try (var loader = URLClassLoader.newInstance(new URL[]{tempDir.toUri().toURL()})) { - newCoreCommClass = loader.loadClass("org.contikios.cooja.corecomm." + className).asSubclass(CoreComm.class); - } catch (IOException | NullPointerException | ClassNotFoundException e1) { - throw new MoteTypeCreationException("Could not load corecomm class file: " + className + ".class", e1); - } - - try { - return newCoreCommClass.getConstructor(File.class).newInstance(libFile); - } catch (Exception e) { - throw new MoteTypeCreationException("Error when creating corecomm instance: " + className, e); - } + @Override + public void removed() { + arena.close(); } } diff --git a/java/org/contikios/cooja/CoreComm.java b/java/org/contikios/cooja/contikimote/CoreComm.java similarity index 51% rename from java/org/contikios/cooja/CoreComm.java rename to java/org/contikios/cooja/contikimote/CoreComm.java index 86308b28c1..127482859b 100644 --- a/java/org/contikios/cooja/CoreComm.java +++ b/java/org/contikios/cooja/contikimote/CoreComm.java @@ -26,50 +26,60 @@ * */ -package org.contikios.cooja; +package org.contikios.cooja.contikimote; +import java.io.File; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; +import java.lang.foreign.SegmentScope; +import java.lang.foreign.SymbolLookup; +import java.lang.invoke.MethodHandle; /** - * The purpose of CoreComm is to communicate with a compiled Contiki system - * using Java Native Interface (JNI). Each implemented class (named - * Lib[number]), loads a shared library which belongs to one mote type. The - * reason for this somewhat strange design is that once loaded, a native library - * cannot be unloaded in Java (in the current versions available). Therefore, if - * we wish to load several libraries, the names and associated native functions - * must have unique names. And those names are defined via the calling class in - * JNI. For example, the corresponding function for a native tick method in - * class Lib1 will be named Java_org_contikios_cooja_corecomm_Lib1_tick. When creating - * a new mote type, the main Contiki source file is built with function - * names compatible with the next available corecomm class. This also implies - * that even if a mote type is deleted, a new one cannot be created using the - * same corecomm class without restarting the JVM and thus the entire - * simulation. - *
- * Each implemented CoreComm class needs read-access to the following core - * variables: - *