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 filesCheerpJ 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 underlyinglib.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:
// Function to add a string file to /str/ from JavaScriptasync 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.
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 applicationasync 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’snio.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. TheREPLACE_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:
- Java writes/generates a file: Your Java application creates or saves a file to the
/files/
mounting point within CheerpJ’s virtual filesystem. - 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.
- 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 aBlob
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.
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.
// 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 applicationasync 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 aBlob
.- 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, andlink.download
suggests a filename to the browser. natives
Object incheerpjInit
: 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
// (Retain the native method declaration from Section 2)public static native void downloadFileFromCheerpJ(String filePath);
// Button to trigger the JFileChooserJButton 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 thatfilePath
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 Codeprivate void downloadDirectURL() {try {// Assumes 'example.txt' is in the same directory as the applet JARURL fileUrl = new URL(getCodeBase().getProtocol(),getCodeBase().getHost(),getCodeBase().getPort(),"/example.txt");getAppletContext().showDocument(fileUrl, "_blank"); // "_blank" will trigger downloadSystem.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 Codeprivate void downloadViaJavaScriptShowDocument() {try {String content = "Dynamic content generated in Java!";String encodedContent = URLEncoder.encode(content, "UTF-8").replace("+", "%20"); // Encode for URLAppletContext context = getAppletContext();// Call a JavaScript function 'downloadFromJava' and pass contentcontext.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.htmlfunction 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)");}