I want to improve the cross platform behavior of a java application. However, its test suite currently assumes the existence of the /tmp directory.
What is the best equivalent location on the windows platform? N.B. I most definitely do not want to assume the user has admin rights but I do want it to work on at least XP, Vista & Windows7.
Is there an existing environment variable that would help, and/or a set of preferred locations I could try in order of preference?
The system property java.io.tmpdir can be used for the user's temp directory:
File tmp = new File(System.getProperty("java.io.tmpdir"));
This may be preferred over File.createTempFile (which, in any case, uses the tmpdir system property under the hood) in the instance where you want to search for temp files (for example, cached data from a previous invocation of your application, which sounds like it might be the case from your question).
You can change the value of the system property by providing a runtime override on the command line (a JVM argument): -Djava.io.tmpdir=C:\foo\bar
Note: the "trailing slash" issue descibed in the comments to seth's answer below can be avoided by using the relevant File constructor:
String fileName = "foobar.txt"
String tmpPath = System.getProperty("java.io.tmpdir");
File tmpFile;
tmpFile = new File(tmpPath + File.separator + fileName); //possible problem
tmpFile = new File(new File(tmpPath), fileName); //OK!
Obviously windows also has an DOS environment variable %TEMP% which could be used from any scripts which you have
Why not use java.io.File.createTempFile()?
Windows defines environment variables TEMP and TMP both giving the current user's temporary folder.
Use File.createTempFile("myPrefix","mySuffix").
If you want to insist on doing something that is windows specific you can use the %temp% environment variable.
Related
How can I change the current working directory from within a Java program? Everything I've been able to find about the issue claims that you simply can't do it, but I can't believe that that's really the case.
I have a piece of code that opens a file using a hard-coded relative file path from the directory it's normally started in, and I just want to be able to use that code from within a different Java program without having to start it from within a particular directory. It seems like you should just be able to call System.setProperty( "user.dir", "/path/to/dir" ), but as far as I can figure out, calling that line just silently fails and does nothing.
I would understand if Java didn't allow you to do this, if it weren't for the fact that it allows you to get the current working directory, and even allows you to open files using relative file paths....
There is no reliable way to do this in pure Java. Setting the user.dir property via System.setProperty() or java -Duser.dir=... does seem to affect subsequent creations of Files, but not e.g. FileOutputStreams.
The File(String parent, String child) constructor can help if you build up your directory path separately from your file path, allowing easier swapping.
An alternative is to set up a script to run Java from a different directory, or use JNI native code as suggested below.
The relevant OpenJDK bug was closed in 2008 as "will not fix".
If you run your legacy program with ProcessBuilder, you will be able to specify its working directory.
There is a way to do this using the system property "user.dir". The key part to understand is that getAbsoluteFile() must be called (as shown below) or else relative paths will be resolved against the default "user.dir" value.
import java.io.*;
public class FileUtils
{
public static boolean setCurrentDirectory(String directory_name)
{
boolean result = false; // Boolean indicating whether directory was set
File directory; // Desired current working directory
directory = new File(directory_name).getAbsoluteFile();
if (directory.exists() || directory.mkdirs())
{
result = (System.setProperty("user.dir", directory.getAbsolutePath()) != null);
}
return result;
}
public static PrintWriter openOutputFile(String file_name)
{
PrintWriter output = null; // File to open for writing
try
{
output = new PrintWriter(new File(file_name).getAbsoluteFile());
}
catch (Exception exception) {}
return output;
}
public static void main(String[] args) throws Exception
{
FileUtils.openOutputFile("DefaultDirectoryFile.txt");
FileUtils.setCurrentDirectory("NewCurrentDirectory");
FileUtils.openOutputFile("CurrentDirectoryFile.txt");
}
}
It is possible to change the PWD, using JNA/JNI to make calls to libc. The JRuby guys have a handy java library for making POSIX calls called jnr-posix. Here's the maven info
As mentioned you can't change the CWD of the JVM but if you were to launch another process using Runtime.exec() you can use the overloaded method that lets you specify the working directory. This is not really for running your Java program in another directory but for many cases when one needs to launch another program like a Perl script for example, you can specify the working directory of that script while leaving the working dir of the JVM unchanged.
See Runtime.exec javadocs
Specifically,
public Process exec(String[] cmdarray,String[] envp, File dir) throws IOException
where dir is the working directory to run the subprocess in
If I understand correctly, a Java program starts with a copy of the current environment variables. Any changes via System.setProperty(String, String) are modifying the copy, not the original environment variables. Not that this provides a thorough reason as to why Sun chose this behavior, but perhaps it sheds a little light...
The working directory is a operating system feature (set when the process starts).
Why don't you just pass your own System property (-Dsomeprop=/my/path) and use that in your code as the parent of your File:
File f = new File ( System.getProperty("someprop"), myFilename)
The smarter/easier thing to do here is to just change your code so that instead of opening the file assuming that it exists in the current working directory (I assume you are doing something like new File("blah.txt"), just build the path to the file yourself.
Let the user pass in the base directory, read it from a config file, fall back to user.dir if the other properties can't be found, etc. But it's a whole lot easier to improve the logic in your program than it is to change how environment variables work.
I have tried to invoke
String oldDir = System.setProperty("user.dir", currdir.getAbsolutePath());
It seems to work. But
File myFile = new File("localpath.ext");
InputStream openit = new FileInputStream(myFile);
throws a FileNotFoundException though
myFile.getAbsolutePath()
shows the correct path.
I have read this. I think the problem is:
Java knows the current directory with the new setting.
But the file handling is done by the operation system. It does not know the new set current directory, unfortunately.
The solution may be:
File myFile = new File(System.getPropety("user.dir"), "localpath.ext");
It creates a file Object as absolute one with the current directory which is known by the JVM. But that code should be existing in a used class, it needs changing of reused codes.
~~~~JcHartmut
You can use
new File("relative/path").getAbsoluteFile()
after
System.setProperty("user.dir", "/some/directory")
System.setProperty("user.dir", "C:/OtherProject");
File file = new File("data/data.csv").getAbsoluteFile();
System.out.println(file.getPath());
Will print
C:\OtherProject\data\data.csv
You can change the process's actual working directory using JNI or JNA.
With JNI, you can use native functions to set the directory. The POSIX method is chdir(). On Windows, you can use SetCurrentDirectory().
With JNA, you can wrap the native functions in Java binders.
For Windows:
private static interface MyKernel32 extends Library {
public MyKernel32 INSTANCE = (MyKernel32) Native.loadLibrary("Kernel32", MyKernel32.class);
/** BOOL SetCurrentDirectory( LPCTSTR lpPathName ); */
int SetCurrentDirectoryW(char[] pathName);
}
For POSIX systems:
private interface MyCLibrary extends Library {
MyCLibrary INSTANCE = (MyCLibrary) Native.loadLibrary("c", MyCLibrary.class);
/** int chdir(const char *path); */
int chdir( String path );
}
The other possible answer to this question may depend on the reason you are opening the file. Is this a property file or a file that has some configuration related to your application?
If this is the case you may consider trying to load the file through the classpath loader, this way you can load any file Java has access to.
If you run your commands in a shell you can write something like "java -cp" and add any directories you want separated by ":" if java doesnt find something in one directory it will go try and find them in the other directories, that is what I do.
Use FileSystemView
private FileSystemView fileSystemView;
fileSystemView = FileSystemView.getFileSystemView();
currentDirectory = new File(".");
//listing currentDirectory
File[] filesAndDirs = fileSystemView.getFiles(currentDirectory, false);
fileList = new ArrayList<File>();
dirList = new ArrayList<File>();
for (File file : filesAndDirs) {
if (file.isDirectory())
dirList.add(file);
else
fileList.add(file);
}
Collections.sort(dirList);
if (!fileSystemView.isFileSystemRoot(currentDirectory))
dirList.add(0, new File(".."));
Collections.sort(fileList);
//change
currentDirectory = fileSystemView.getParentDirectory(currentDirectory);
I try to write and read to the file in my java project file called Books.txt.
The problem is that I can access the file only if partialPath has full path to the file.
Here is the code:
public <T> List<T> readFromFile(String fileName) {
private String partialPath = "\\HW3\\src\\java\\repos\\";
try {
String path = partialPath + fileName;
FileInputStream fi = new FileInputStream(path);
ObjectInputStream oi = new ObjectInputStream(fi);
// Read objects
List<T> items = (List<T>) oi.readObject();
oi.close();
fi.close();
return items;
} catch (IOException | ClassNotFoundException e) {
}
}
If I set relative path as above I get exception file not found.
My question is how can I set full path to the current directory programmatically?
Here is a code snippet of the Drombler Commons - Client Startup code I wrote, to determine the location of the executable jar. Replace DromblerClientStarter with your main class.
This should work at least when you're running your application as an executable JAR file.
/**
* The jar URI prefix "jar:"
*/
private static final String FULL_JAR_URI_PREFIX = "jar:";
/**
* Length of the jar URI prefix "jar:"
*/
private static final int FULL_JAR_URI_PREFIX_LENGTH = 4;
private Path determineMainJarPath() throws URISyntaxException {
Class<DromblerClientStarter> type = DromblerClientStarter.class;
String jarResourceURIString = type.getResource("/" + type.getName().replace(".", "/") + ".class").toURI().
toString();
int endOfJarPathIndex = jarResourceURIString.indexOf("!/");
String mainJarURIString = endOfJarPathIndex >= 0 ? jarResourceURIString.substring(0, endOfJarPathIndex)
: jarResourceURIString;
if (mainJarURIString.startsWith(FULL_JAR_URI_PREFIX)) {
mainJarURIString = mainJarURIString.substring(FULL_JAR_URI_PREFIX_LENGTH);
}
Path mainJarPath = Paths.get(URI.create(mainJarURIString));
return mainJarPath;
}
Depending on where you bundle Books.txt in your application distribution package, you can use this mainJarPath to determine the path of Books.txt.
I also feel that files created (and later possibly modified and or deleted) by your running Java application is usually better to be placed in a location of the file system that is away from your java application installed home directory. An example might be the 'C:\ProgramData\ApplicationNameFiles\' for the Windows operating system or something similar for other OS platforms. In my opinion, at least for me, I feel it provides less chance of corruption to essential application files due to a poorly maintained drive or, accidental deletion by a User that opens up a File Explorer and decides to take it upon him/her self to clean their system of so called unnecessary files, and other not so obvious reasons.
Because Java can run on almost any platform and such data file locations are platform specific the User should be allowed to select the location to where these files can be created and manipulated from. This location then can be saved as a Property. Indeed, slightly more work but IMHO I feel it may be well worth it.
It is obviously much easier to create a directory (folder) within the install home directory of your JAR file when it's first started and then store and manipulate your application's created data files from there. Definitely much easier to find but then again...that would be a matter of opinion and it wouldn't be mine. Never-the-less if you're bent on doing it this way then your Java application's Install Utility should definitely know where that install path would be, it is therefore just a matter of storing that location somewhere.
No Install Utility? Well then your Java application will definitely need a means to know from where your JAR file is running from and the following code is one way to do that:
public String applicationPath(Class mainStartupClassName) {
try {
String path = mainStartupClassName.getProtectionDomain().getCodeSource().getLocation().getPath();
String pathDecoded = URLDecoder.decode(path, "UTF-8");
pathDecoded = pathDecoded.trim().replace("/", File.separator);
if (pathDecoded.startsWith(File.separator)) {
pathDecoded = pathDecoded.substring(1);
}
return pathDecoded;
}
catch (UnsupportedEncodingException ex) {
Logger.getLogger("applicationPath() Method").log(Level.SEVERE, null, ex);
}
return null;
}
And here is how you would use this method:
String appPath = applicationPath(MyMainStartupClassName.class);
Do keep in mind that if this method is run from within your IDE it will most likely not return the path to your JAR file but instead point to a folder where your classes are stored for the application build.
This is not a unique issue to Java, it's a problem faced by any developer of any language wishing to write data locally to the disk. The are many parts to this problem.
If you want to be able to write to the file (and presumably, read the changes), then you need to devise a solution which allows you find the file in a platform independent way.
Some of the issues
The installation location of the program
While most OS's do have some conventions governing this, this doesn't mean they are always used, for what ever reason.
Also, on some OS's, you are actively restricted from writing to the "installation" location. Windows 8+ doesn't allow you to write to the "Program Files" directory, and in Java, this usually (or at least when I was dealing with it) fails silently.
On MacOS, if you're using a "app bundle", the working directory is automatically set to the user's home directory, making it even more difficult to manage
The execution context (or working directory) may be different from the installation location of the program
A program can be installed in one location, but executed from a different location, this will change the working directory location. Many command line tools suffer from this issue and use different conventions to work around it (ever wonder what the JAVA_HOME environment variable is for 🤔)
Restricted disk access
Many OS's are now actively locking down the locations to which programs can write, even with admin privileges.
A reusable solution...
Most OS's have come up with conventions for solving this issue, not just for Java, but for all developers wishing to work on the platform.
Important Like all guide lines, these are not hard and fast rules, but a recommendations made by the platform authors, which are intended to make your life simpler and make the operation of the platform safer
The most common solution is to simply place the file in a "well known location" on the disk, which can be accessed through an absolute path independently of the installation or execution location of the program.
On Windows, this means placing the file in either ~\AppData\Local\{application name} or ~\AppData\Roaming\{application name}
On MacOS, this means placing the file in ~/Library/Application Data/{application name}
On *nix, this typically means placing the file in ~/.{application name}
It could be argued that you could use ~/.{application name} on all three platforms, but as a user who "shows hidden files", I'd prefer you didn't pollute my home directory.
A possible, reusable, solution...
When Windows 8 came out, I hit the "you can't write to the Program Files" issue, which took some time to diagnose, as it didn't generate an exception, it just failed.
I was also working a lot more on Mac OS as well, so I needed a simple, cross platform solution, so my code could automatically adapt without the need for multiple branches per platform.
To this end, I came with a simple utility class...
public enum SystemUtilities {
INSTANCE;
public boolean isMacOS() {
return getOSName().startsWith("Mac");
}
public boolean isMacOSX() {
return getOSName().startsWith("Mac OS X");
}
public boolean isWindowsOS() {
return getOSName().startsWith("Windows");
}
public boolean isLinux() {
return getOSName().startsWith("Linux");
}
public String getOSName() {
return System.getProperty("os.name");
}
public File getRoamingApplicationSupportPath() {
// For *inx, use '~/.{AppName}'
String path = System.getProperty("user.home");
if (isWindowsOS()) {
path += "\\AppData\\Roaming";
} else if (isMacOS()) {
path += "/Library/Application Support";
}
return new File(path);
}
public File getLocalApplicationSupportPath() {
// For *inx, use '~/.{AppName}'
String path = System.getProperty("user.home");
if (isWindowsOS()) {
path += "\\AppData\\Local";
} else if (isMacOS()) {
path += "/Library/Application Support";
}
return new File(path);
}
}
This provides a baseline from which "independent" code can be built, for example, you could use something like...
File appDataDir = new File(SystemUtilities.INSTANCE.getLocalApplicationSupportPath(), "MyAwesomeApp");
if (appDataDir.exists() || appDataDir.mkdirs()) {
File fileToWrite = new File(appDataDir, "Books.txt");
//...
}
to read/write to the file. Although, personally, I might have manager/factory do this work and return the reference to the end File, but that's me.
What about "pre-packaged" files?
Three possible solutions...
Create the file(s) if they don't exist, populating them with default values as required
Copy "template" file(s) out of the Jar file, if they don't exist
Use an installer to install the files - this is the solution we used when we were faced with changing the location of all our "external" configuration files.
Read only files...
For read only files, the simplest solution is to embedded them within the Jar as "embedded resources", this makes it easier to locate and manage...
URL url = getClass().getResource("/path/to/readOnlyResource.txt");
How you do this, will depend on your build system
In my program, I am reading a resource file for a unit test. I use file path as:
\\\path\\\to\\\file
On my machine(Windows) this runs fine. But on server(Unix), this fails, and I have to change it to: /path/to/file
But Java is supposed to be platform independent. So isn't this behaviour unexpected?
Use FileSystem.getSeparator() or System.getProperty("file.separator") instead of using slashes.
EDIT:
You can get an instance of FileSystem via FileSystems.getDefault (JDK 1.7+)
You can use File.separator to get the appropriate character in a platform-independent way.
Java is platform independent. The file path-es and some system calls are not.
As long as the path is relative, you can use File.separator:
String path = "path" + File.separator + "to" + File.separator + "file";
System.out.println(path); // prints path\to\file on windows
Sometimes it's an option is to provide a Properties file and let the user define path of that actual file. This way full paths are okay too. You can read the properties like this:
Properties props = new Properties();
props.load(new FileInputStream(filePath));
The next question is: how to specify the location of that file? That might be either a file on a relative path. If that's not viable for your app, then you can let the user specify it in a system property:
java ... -DconfigFile=C:\TEMP\asd.txt .... -jar myapp.jar
Then you can access it like this:
// prints C:\TEMP\asd.txt if you specified -DconfigFile=C:\TEMP\asd.txt
System.out.println(System.getProperty("configFile"));
This is the expected behaviour.
Java code compiles on any machine/OS provided you have the right version of Java installed on it.
However, at run time, your code sees only a variable value like another one, which happens to be \path\to\file
When it talks to the file system, it uses that particular value ; the file system then tries to find that path you've given to it ; which is why one syntax works fine on Windows but will not work on Linux.
Better way of doing this is :
val pathUri = Paths.get(".//src//test//res//file.txt").toUri()
val is = FileInputStream((File(pathUri)))
We know if we want to address a file, we could do it to something like this:
// file_path is the path file to current position of program
String address = file_path + File.separator + "output.txt";
But I made a mistake and set file separator twice, like this:
String address = file_path + File.separator + File.separator + "output.txt";
These code are just an example and I get the file path by Java library which is cross platform and there is not any mistake. I show them like this to show my mistake more clearly.
In fact, I want to rename a text file from a_text.txt to b_text.txt but I set two file separator before these names! something like //b_text.txt not /b_text.txt
In Windows there is no error and file could read or edit, but what about in other Operation Systems like Linux, Mac and Solaris?
Since I don't have those systems, I want to know about it. Although I will solve that mistake as soon as possible but I'm curious to know about it anyway.
As #Yu-LinChen stated, use Path if you're using Java 7 or higher. Everything else will be platform dependent.
Moreover, as the D: suggests you're also using an absolute path, so it will be even more restricted to a certain environment. Consider making the path configurable, e.g. in a properties file, and read the configuration using Path.get(pathFromProperty). This will work with both D:\some\path on Windows and with /some/path on Unix-like systems.
I need to execute a java application using ProcessBuilder. I also need to use the same java executable in the called process that is being used by the calling application.
Is there a platform independent way to retrieve the current java executable path and file name?
I've seen some code snippets similar to this:
public static boolean isWindows() {
return (System.getProperty("os.name").toLowerCase().indexOf("win") >= 0);
}
public static String getJavaExecutablePath() {
String executable = "java";
if (isWindows()) {
executable = executable + ".exe";
}
File path = new File(System.getProperty("java.home"), "/bin/" + executable);
return path.getAbsolutePath();
}
The above code will probably need to be improved to use File.separator.
Is the above all that is required, or is there something else to consider, for example case sensitivity?
Ideally, there would be a library available for getting the OS path, but that is probably a question for a different forum.
First, the most important thing for your requirement is System.getProperty("java.home"), you've got it already.
Secondly, don't worry about case sensitivity, the path '$JAVA_HOME/bin/java' always be in lower case in every java release, and there are already many other projects depend on this.
Last thing, you don't have to use a '/' or '\' to build the absolute path, consider this:
File path = new File(new File(System.getProperty("java.home"), "bin"), executable);
As you mentioned the File.separator did similar thing, but that is talking about a string, and File talks about file.
Here is a discussion about File path