Editing a text-file stored inside a JAR

Ok, I have a .txt file stored inside a folder “res” inside my JAR-file. I can read from that file with the following method.


	public String[] loadText(String path){
		InputStream mySettingsStream = FileLoader.class.getResourceAsStream("/res/"+path);
		String mySettingsString = "null";
		StringBuilder build = new StringBuilder();
		int i = 0;
		try {
			while((i = mySettingsStream.read()) != -1) {
				build.append((char)i);
			}
			mySettingsString = build.toString();
			return mySettingsString.split("\r\n");
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
	}

However, I want to be able to store changes I’ve made to the data in that file and I can’t figure out how to do that. I tried using an OutputStreamWriter with a URL (as you can see below) but it caused a “java.net.UnknownServiceException: protocol doesn’t support output” so I clearly need something else.
If possible I want to even be able to create a new .txt file at the location in case one with the name from path doesn’t exist.


	public void write(String path, String[] lines){
		if(path==null || lines==null){return;}
		URL url = FileLoader.class.getResource("/res/" + path);
		try {
			URLConnection connection = url.openConnection();
			connection.setDoOutput(true);
			OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream());
			for(int i = 0; i < lines.length; i++){
				out.write(lines[i]);
			}
			out.close();
		} catch (IOException e) {
			System.err.println("Failed to write to file");
			e.printStackTrace();
		}
	}

Anyone have any ideas?
I know that it might not be the best idea to mess around with changing and creating files inside a JAR file but it’d be great if I could do it safely. At the very least I’d like to have the ability to edit text files in there.

There isn’t a great way to do this. Jars are read-only.

You could hack something together that uses a piggybacked jar that you run upon shutdown that repacks a new jar with the file to be saved. Actually I plan on adding that feature to SvgExe, but it’s quite a bit of a hack and breaks things like jar signing.

Instead, you might just want to save the file to a temp directory.

Maybe that “read only” is the reason why this doesn’t work even though I get no errors/exceptions with my code.


	public void write(String path, String[] lines){
		if(path==null || lines==null){return;}
		URL url = FileLoader.class.getResource("/res/" + path);
		System.out.println("The URL we got is " + url.toString()); 
		try {
			File file = new File(url.toURI());
			PrintWriter out = new PrintWriter(file);
			for(int i = 0; i < lines.length; i++){
				System.out.println("Appending..."); 
				out.append(lines[i]);
				out.append("\r\n");	
			}
			out.flush();
			out.close();
		} catch (IOException e) {
			System.err.println("Failed to write to file");
			e.printStackTrace();
		} catch (URISyntaxException e) {
			System.err.println("Failed to write to file");
			e.printStackTrace();
		}
	}

In that case I just have one question. Storing save information outside of the JAR file is ok but I have a wish there. Instead of having a dozen files in the same folder as the JAR, I’d like to create a folder next to the JAR and save them in there. Is there a simple way to check if there is a folder with a certain name next to the JAR and, in case it doesn’t exist, creating it?

This should work:


try {
    // Get the location of the application...
    URL appLocation = this.getClass().getProtectionDomain().getCodeSource().getLocation();
    // Create a URI that puts our new folder location relative to the applications location...
    URI folderLocation = new URL(appLocation, "My/Folder/Location").toURI();
    // We'll let the file object handle the rest of the heavy lifting...
    File f = new File(folderLocation);
    // Does the file/folder already exist...?
    if (!f.exists()) {
        // If not, try to create it...
        if(!f.mkdirs()) {
            // If we find ourselves here, something went wrong when creating the directory 
            // structure...act accordingly.
        }
    }
}
catch(MalformedURLException mfu) {
    // Handle MalformedURLException
}
catch(URISyntaxException use) {
    // Handle URISyntaxException
}

IMHO, this isn’t the best way to handle it. Consider the scenario where your jar file is being run under Windows. Everything will work as expected as long as the .jar file is in a folder that the user has write access to. If however, the jar file is placed into a system folder such as “Program Files (x86)” or similar, then the application will not be able to create the directory structure, or write to your settings file unless it was launched with administrative privileges. You’re probably better off making your settings folder/file(s) in a location somewhere within the users home directory. Just my $0.02. :slight_smile:

@CodeHead
URI folderLocation = new URL(appLocation, “My/Folder/Location”).toURI();
This worked while in Eclipse, and created new Folders in the “bin”. But once exported it didn’t work and it instead gave me
“rsrc:My/Folder/Location/test.txt”
Do you have any idea why this happens?

Hmm, hard to say. I develop in NetBeans and it worked when ran inside of the IDE as well as when ran as a stand alone application. I can only make a guess that maybe it has something to do with the way Eclipse exports/packages runnable .jar files. ???

What does the output of the below code look like when run inside of the IDE vs when run from an exported .jar file?


try {
    String fs = System.getProperty("file.separator");
    URL appURL = this.getClass().getProtectionDomain().getCodeSource().getLocation();
    String appLocation = URLDecoder.decode(appURL.getFile(), "UTF-8");
    File myFile = new File(new File(appLocation).getParent(), "My" + fs + "Directory" + fs);
    JOptionPane.showMessageDialog(null, myFile.getCanonicalPath());
} catch (UnsupportedEncodingException ex) {
    ex.printStackTrace();
} catch (IOException ex) {
    ex.printStackTrace();
}

Simply creating a relative File object will point it to the same folder as your JAR:


File file = new File("my text file.txt"); //Ends up right next to the jar.

Example:

FileOutputStream fos = new FileOutputStream("log.txt");

Good catch. 8) I seem to remember that not always being the case (which would explain the plethora of answers to similar questions on the Internet). One caveat that I noticed which is a non issue for the OP’s use case is that when run in the IDE (at least in NetBeans), the file is not created in the expected directory. In my case, the file was created in the top level of the project folder whereas the verbose method created the file in the same directory as the .class file. Either way, your method is definitely a lot cleaner. LOL

Not on Mac OS X. The workdir will be ~ (user home)

Are you sure? I’ve had my engine tested on OS X and I haven’t had any problem with people saying they can’t find the log file when I ask for it…

With Java 7, which added Filesystem implementations in nio2, there is a something called ZipFileSystem.

You can open a zip file as a Filesystem and then do normal File operations as you do all the time, which operate directly on the Jar.
An URL to your running Jar can be retrieved by the getURLs method of the classloader.