Filesystem

Interacting with the virtual filesystem in CheerpJ

CheerpJ provides a virtual filesystem that allows your Java applications to perform file operations like reading and writing, just as they would on a desktop. This guide will explain how to effectively use CheerpJ’s virtual filesystem, demonstrating how to transfer files between your Java application and the local JavaScript environment by focusing on common use cases.

CheerpJ’s Virtual Filesystem: Mounting Points Explained

CheerpJ’s virtual filesystem has different mounting points, each with specific purposes:

  • /app/: This mount point is read-only. It corresponds to the root of the web server and contains all the files that are part of your Java application, including your .jar files and any resources bundled within them. Java can read files from /app/, but it cannot write to this location.
  • /files/: This is a persistent, in-memory filesystem. Files written here are accessible by your Java application and persist across sessions and page reloads until the user manually clears their browser’s IndexedDB. This is useful for user-generated data, or situations where you need to move files around within the virtual filesystem.
  • /str/: This mounting point is for adding string content directly from JavaScript into the virtual filesystem. This is useful for providing configuration files or initial data to your Java application without needing to package them inside your JAR.

Read more about the filesystem in the CheerpJ documentation.

Local files
CheerpJ provides access to a virtualized filesystem, which does not correspond to the local user’s computer. For browser security reasons, direct access to local files is forbidden. This means your Java code running in CheerpJ cannot directly read or write files on the user’s physical hard drive. All file interactions happen within this secure, virtual environment.

1. Loading Files from JavaScript into the Virtual Filesystem

Often, you might have files on your web page (e.g., uploaded by a user) that your Java application needs to process. You can transfer these files from the JavaScript environment into CheerpJ’s virtual filesystem. This is useful for:

  • External Applications: Sometimes, your Java application expects files to be in a specific location (e.g., a hardcoded path like “filename.txt”). If you load a file into /app/, your Java code would need to reference it as /app/filename.txt. However, if your Java application is an external application that you cannot modify, it might not be able to change this prefix. Loading the file into /files/ allows Java to access it using just “filename.txt”, as /files/ behaves more like a default working directory.
  • Dynamic Content (e.g., Configuration Files): You can provide dynamic content, such as configuration files, to your Java application directly from JavaScript, without recompiling your Java code.
  • Binary Data: The cheerpOSAddStringFile API (and the underlying lib.java.nio.file.Files.copy method) can handle binary data, making it useful for providing various file types to Java.

Method 1: Using cheerpOSAddStringFile API (for string or binary data)

This is the simplest way to add a file from JavaScript to the virtual filesystem. It’s particularly useful for providing string content, but can also handle binary data.

Important: cheerpOSAddStringFile must be called after cheerpjInit() has completed (i.e., its Promise has resolved), as it relies on the CheerpJ runtime environment and its virtual filesystem being initialized.

The cheerpOSAddStringFile API allows you to create a file in the /str/ mount point, which can then be accessed by your Java application as if it were a regular file.

Example:

index.html
// Function to add a string file to /str/ from JavaScript
async function addStringFileToCheerpJ() {
const fileName =
document.getElementById("jsFileName").value.trim() || "default.txt";
const content =
document.getElementById("jsFileContent").value || "Default content.";
const messageDiv = document.getElementById("jsMessage");
try {
// Using cheerpOSAddStringFile to add content to /str/
await cheerpOSAddStringFile("/str/" + fileName, content);
messageDiv.textContent = `File "${fileName}" added successfully to /str/.`;
console.log(`File "${fileName}" added to /str/.`);
} catch (e) {
messageDiv.textContent = `Error adding file to /str/: ${e.message}`;
console.error("Error writing file to /str/:", e);
}
}

Method 2: Using library mode

If you have a file that’s already part of your /app/ mount point, but your Java application needs to access it as if it were in /files/ (without the /app/ prefix), you can copy it using library mode. You can use the cheerpjRunLibrary API to access Java’s nio.file APIs, which allow you to copy files from one mount point to another.

index.html
async function copyFileToFilesMountPoint() {
// cheerpjRunLibrary can be called before cheerpjInit
const lib = await cheerpjRunLibrary(""); // Create a library mode object
const Files = await lib.java.nio.file.Files;
const StandardCopyOption = await lib.java.nio.file.StandardCopyOption;
const Paths = await lib.java.nio.file.Paths;
// Source is a file within the /app/ mount point
const source = await Paths.get(`/app/notes_tmp.txt`, []);
// Target is the /files mount point
const target = await Paths.get(`/files/notes_tmp.txt`, []);
try {
await Files.copy(source, target, [StandardCopyOption.REPLACE_EXISTING]);
console.log("File copied from /app/ to /files/ successfully!");
} catch (e) {
console.error("Error copying file:", e);
}
}
// Then, initialize CheerpJ and run your Java application
async function cj3init() {
await cheerpjInit({
version: 8,
});
// cheerpjCreateDisplay(...) and cheerpjRunMain(...) go here
}
cj3init();
copyFileToFilesMountPoint();
// Your Java app can now access "notes_tmp.txt" by simply calling `new File("notes_tmp.txt")`
// without needing the /app/ prefix.

Explanation:

  • cheerpjRunLibrary(""): This creates a library mode object that allows you to use Java’s nio.file APIs in JavaScript. You can read more about library mode in the CheerpJ documentation.
  • Files.copy(source, target, [StandardCopyOption.REPLACE_EXISTING]): This copies the file from the /app/ mount point to the /files/ mount point. The REPLACE_EXISTING option ensures that if the target file already exists, it will be overwritten.
  • Accessing the File in Java: After copying, your Java application can access the file simply by using new File("notes_tmp.txt"), without needing to specify the /app/ prefix.

2. Getting Files from the Virtual Filesystem to the Local Filesystem (Java to JavaScript)

This is a very common requirement: your Java application generates or modifies a file, and you need to access the file locally. It’s important to note that when your Java application saves a file while running with CheerpJ, this file will be written to CheerpJ’s virtual filesystem (the /files/ mount point) rather than directly to the user’s local file system. The general workflow for then making this file available to the user is as follows:

  1. Java writes/generates a file: Your Java application creates or saves a file to the /files/ mounting point within CheerpJ’s virtual filesystem.
  2. Java calls a JavaScript native method: Since the file is in the virtual filesystem, we can implement a native to retrieve it in JavaScript. To learn more about native methods, you can refer to the CheerpJ tutorial on native methods.
  3. JavaScript retrieves and processes the file: The JavaScript native method retrieves the file’s content from the virtual filesystem. At this point, the JavaScript native code can process the file in any way required by your web application. For example you can trigger a download by using the cjFileBlob() API to get the file’s content as a Blob and then use standard browser APIs to trigger a download (as will be explained in a later section).

Step 1: Java Writes the File and Calls a Native Method

First, your Java application needs to write the file to CheerpJ’s virtual filesystem, specifically to the /files/ mount point. Once the file is written, Java can call a native method which acts as a bridge to execute JavaScript code.

App.java
public static native void downloadFileFromCheerpJ(String filePath); // Declare the native method
/*
Rest of your Java code...
*/
try {
File file = new File("/files/example.txt"); // You can also use "example.txt" since it defaults to the /files/ mount point
FileWriter writer = new FileWriter(file);
writer.write(content);
writer.close();
downloadFileFromCheerpJ(filePath);
} catch (IOException e) {
System.err.println("Error generating or writing file: " + e.getMessage());
}

Step 2: JavaScript Implements the Native Method and Downloads the File

Now, we need to provide the JavaScript implementation for the downloadFileFromCheerpJ native method. This JavaScript function will receive the file path from Java, use CheerpJ’s cjFileBlob() API to get the file’s content, and then use standard browser APIs to trigger a download.

index.html
// JavaScript implementation of the native method
// The naming convention is Java_<fully-qualified-class-name>_<method-name>
async function Java_com_App_downloadFileFromCheerpJ(
lib, // CheerpJ's internal library object (usually not needed here)
fileName // The file name passed from Java
) {
console.log(`Attempting to download file: ${fileName}`);
try {
// 3. Use cjFileBlob() to get the file content as a Blob
const blob = await cjFileBlob(`${fileName}`);
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
// Set the downloaded file name as the last part of the path
link.download = fileName.split("/").pop(); // Extract the file name from the path
document.body.appendChild(link); // Append to body to make it clickable
link.click(); // Programmatically click the link to trigger download
document.body.removeChild(link); // Clean up
URL.revokeObjectURL(link.href); // Release the object URL
console.log(`Successfully downloaded ${fileName}`);
} catch (e) {
console.error("Error downloading file:", e);
alert("Failed to download file. Check console for details.");
}
}
// Initialize CheerpJ and run the Java application
async function cj3init() {
await cheerpjInit({
version: 8,
// Register the native method
natives: {
Java_com_App_downloadFileFromCheerpJ,
},
});
// cheerpjCreateDisplay(...) and cheerpjRunMain(...) go here
}
cj3init();

Explanation:

  • Native Method Naming Convention: The JavaScript function name must follow the pattern Java_<fully-qualified-class-name>_<method-name>. This is how CheerpJ links your Java native method call to the correct JavaScript function.
  • cjFileBlob(filePath): This CheerpJ API is the bridge between the virtual filesystem and JavaScript. It takes the path to a file within /files/ (or /app/, /str/) and returns its content as a Blob.
  • Blob and Download: Once you have the Blob, standard browser techniques are used to create a temporary download link and simulate a click, prompting the user to save the file. URL.createObjectURL(blob) generates a temporary URL for the blob, and link.download suggests a filename to the browser.
  • natives Object in cheerpjInit: This is where you register all your custom JavaScript native methods with CheerpJ, making them accessible from your Java code.

3. Using Java’s JFileChooser with CheerpJ

Java applications often use JFileChooser for “Save As…” dialogues. CheerpJ supports JFileChooser, and when a user selects a file name, that name is used within the virtual filesystem. You can then use this name to trigger a download using the native method approach described in Section 2.

When you instantiate JFileChooser in your Java code, it operates on CheerpJ’s virtual filesystem. You instantiate it without arguments (JFileChooser fileChooser = new JFileChooser();), to open the dialog in the default directory: /files/.

Here is how the dialog will look like when you use JFileChooser in CheerpJ with the default /files/ mount point:

Java Source Code: Essential JFileChooser and Download Logic

App.java
// (Retain the native method declaration from Section 2)
public static native void downloadFileFromCheerpJ(String filePath);
// Button to trigger the JFileChooser
JButton chooseDownloadButton = new JButton("Choose File to Download");
chooseDownloadButton.addActionListener((ActionEvent e) -> {
// 1. Instantiate JFileChooser, initially pointing to the /files/ mount point.
JFileChooser fileChooser = new JFileChooser("/files/");
fileChooser.setDialogTitle("Select file to download");
// 2. Show the save dialog to let the user select a file.
int result = fileChooser.showSaveDialog(this);
// 3. Check if the user approved the selection (didn't cancel).
if (result == JFileChooser.APPROVE_OPTION) {
// 4. Get the selected file from the file chooser.
// For example, if you wrote "Hello World" to /files/example.txt, "/files/example.txt" will be the selected file.
// 5. Get the path of the selected file.
File selectedFile = fileChooser.getSelectedFile();
String filePath = selectedFile.getPath();
// 6. Call the native JavaScript method to download the file from the virtual filesystem.
downloadFileFromCheerpJ(filePath);
}
});

Explanation:

  • new JFileChooser("/files/"): This initializes the file chooser to start Browse from the /files/ directory within CheerpJ’s virtual filesystem. This is important because /files/ is the writable and persistent mount point.
  • fileChooser.showSaveDialog(this): This method displays the file chooser dialog, allowing the user to select a save location and filename. showOpenDialog would be used for opening existing files.
  • fileChooser.getSelectedFile().getPath(): If the user approves the selection, this returns the full path (e.g., /files/myDocument.txt) that the user specified.
  • downloadFileFromCheerpJ(filePath): We reuse the same native method from Section 2. Once the user has selected a path, you would ensure your Java application writes its data to that filePath within the virtual filesystem, and then you would call this native method to trigger the download to the user’s local machine.

HTML and JavaScript Code (index.html)

The HTML and JavaScript for handling the download are essentially the same as in Section 2.

4. Special Case: Applets and showDocument()

For Applets, AppletContext.showDocument() can be used as a workaround for downloads, although native methods are generally more flexible.

  • Direct URL Download: If the file you want to download is already available at a public URL (e.g., hosted on your server), you can simply use showDocument() to point to that URL, and the browser will handle the download.

    // Java Applet Code
    private void downloadDirectURL() {
    try {
    // Assumes 'example.txt' is in the same directory as the applet JAR
    URL fileUrl = new URL(getCodeBase().getProtocol(),
    getCodeBase().getHost(),
    getCodeBase().getPort(),
    "/example.txt");
    getAppletContext().showDocument(fileUrl, "_blank"); // "_blank" will trigger download
    System.out.println("Triggered direct URL download: " + fileUrl.toString());
    } catch (Exception ex) {
    ex.printStackTrace();
    JOptionPane.showMessageDialog(null, "Error downloading from URL: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
    }
    }
  • Executing JavaScript for Dynamic Content: You can use showDocument() to execute arbitrary JavaScript code that then handles a download. This is less common but can be useful for passing dynamically generated content from Java to JavaScript for download.

    // Java Applet Code
    private void downloadViaJavaScriptShowDocument() {
    try {
    String content = "Dynamic content generated in Java!";
    String encodedContent = URLEncoder.encode(content, "UTF-8").replace("+", "%20"); // Encode for URL
    AppletContext context = getAppletContext();
    // Call a JavaScript function 'downloadFromJava' and pass content
    context.showDocument(new URL("javascript:downloadFromJava('" + encodedContent + "')"));
    System.out.println("Triggered JS download via showDocument.");
    } catch (Exception ex) {
    ex.printStackTrace();
    }
    }

    And in your HTML, you’d have the downloadFromJava JavaScript function:

    // JavaScript in applet.html
    function downloadFromJava(encodedContent) {
    const content = decodeURIComponent(encodedContent);
    const blob = new Blob([content], { type: "text/plain" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = "applet_showdocument_dynamic.txt";
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
    console.log("File downloaded via showDocument (JS)");
    }