30

I have developed a number of classes which manipulate files in Java. I am working on a Linux box, and have been blissfully typing new File("path/to/some/file");. When it came time to commit I realised some of the other developers on the project are using Windows. I would now like to call a method which can take in a String of the form "/path/to/some/file" and, depending on the OS, return a correctly separated path.

For example:
"path/to/some/file" becomes "path\\to\\some\\file" on Windows.
On Linux it just returns the given String.

I realise it wouldn't take long to knock up a regular expression that could do this, but I'm not looking to reinvent the wheel, and would prefer a properly tested solution. It would be nice if it was built in to the JDK, but if it's part of some small F/OSS library that's fine too.

So is there a Java utility which will convert a String path to use the correct File separator char?

Grundlefleck
  • 124,925
  • 25
  • 94
  • 111

12 Answers12

51

Apache Commons comes to the rescue (again). The Commons IO method FilenameUtils.separatorsToSystem(String path) will do what you want.

Needless to say, Apache Commons IO will do a lot more besides and is worth looking at.

BullyWiiPlaza
  • 17,329
  • 10
  • 113
  • 185
Brian Agnew
  • 268,207
  • 37
  • 334
  • 440
  • 16
    Why do people keep suggesting adding bloated dependencies to avoid writing one line of code? – cletus Nov 08 '09 at 17:54
  • 16
    I don't regard Apache Commons as a bloated dependency. It does so much that I tend to regard Apache Commons Lang and IO as near-essential nowadays. – Brian Agnew Nov 08 '09 at 17:55
  • 7
    Plus, how many bugs do you find in single lines of code ? How many assumptions and edge-cases get missed ? – Brian Agnew Nov 08 '09 at 17:57
  • Because an external dependency has never had or introduced a bug? – cletus Nov 08 '09 at 17:59
  • 1
    To be honest it's almost inevitable that a project your are working on will have a dependent jar and that jar is going to have a Commons Collections/Lang/BeanUtils dependency (I'm surprised at how often this is true) -- so you would already have 1 or more Commons jars for runtime dependency so you could just use it for your compile time – non sequitor Nov 08 '09 at 18:10
  • 10
    Using commons has a couple of advantages in my particular scenario: it comes with a decent level of a guarantee that it will work and is tested; it's self documenting, unlike a regex which I would extract into a static helper method anyway; it's a university project, so not all of us are well versed in regular expressions. Looking at the source, the solution in commons is pretty similar to what you posted, cletus, so it doesn't make much difference. I did specify in the question that using a small library is fine, so +1. – Grundlefleck Nov 08 '09 at 18:31
  • 2
    @Cletus: it seems possible that he may already depend on commons-io if he's doing work with Files. – Paul Morie Nov 08 '09 at 22:23
  • I don't see why in anything other than the most constrained scenarios using a Commons IO/Lang library is unusual or unwarranted. – Brian Agnew Nov 08 '09 at 22:26
18

A "/path/to/some/file" actually works under Windows Vista and XP.

new java.io.File("/path/to/some/file").getAbsoluteFile()

> C:\path\to\some\file

But it is still not portable as Windows has multiple roots. So the root directory has to be selected in some way. There should be no problem with relative paths.

Edit:

Apache commons io does not help with envs other than unix & windows. Apache io source code:

public static String separatorsToSystem(String path) { 
    if (path == null) {
     return null;
    }
    if (isSystemWindows()) {
      return separatorsToWindows(path);
    } else {
      return separatorsToUnix(path);
    }
}
Dwhitz
  • 1,250
  • 7
  • 26
  • 38
Thomas Jung
  • 32,428
  • 9
  • 84
  • 114
  • Oh, I should add: the root disk which is going to be used is the same root disk as where the current working directory is (from where you have executed the Java code in question). – BalusC Nov 08 '09 at 18:05
  • See edit in my answer. Same as the commons io code. Will work with Windows and Unix (Linux, MacOs) – Thomas Jung Nov 08 '09 at 19:42
  • All windows platforms supports it. All UNIX platforms (and clones like Linux, Mac, AIX, Solaris and so on) supports it. I can only not tell from experience if it works in for example JSOS, Amiga, C64 and so on, but you don't need to worry as there's no JVM out for them (yet?). – BalusC Nov 08 '09 at 19:44
  • VMS or OS/390? There are some Java implementations for these. – Thomas Jung Nov 08 '09 at 20:01
  • @Thomas: both falls under UNIX clones (as does HP-UX, which I just recalled). I can also tell from experience that it works that way; I've worked with them in my old IBM ages. – BalusC Nov 08 '09 at 20:04
  • @BalusC - I have no experience with these but a friend of mine worked with some IBM OS that did not have a hierarchical file system. All files were named per convention to emulate directories. – Thomas Jung Nov 08 '09 at 20:12
  • @Grundlefleck - The irony is that I use commons io heavily (mostly http://commons.apache.org/io/api-1.4/org/apache/commons/io/IOUtils.html) so I was interested what was going on. – Thomas Jung Nov 08 '09 at 21:21
8

This is what Apache commons-io does, unrolled into a couple of lines of code:

String separatorsToSystem(String res) {
    if (res==null) return null;
    if (File.separatorChar=='\\') {
        // From Windows to Linux/Mac
        return res.replace('/', File.separatorChar);
    } else {
        // From Linux/Mac to Windows
        return res.replace('\\', File.separatorChar);
    }
}

So if you want to avoid the extra dependency, just use that.

jkh6100
  • 129
  • 2
  • 11
Daniel Winterstein
  • 2,418
  • 1
  • 29
  • 41
3

With the new Java 7 they have included a class called Paths this allows you to do exactly what you want (see http://docs.oracle.com/javase/tutorial/essential/io/pathOps.html)

here is an example:

String rootStorePath = Paths.get("c:/projects/mystuff/").toString();
Gigas64
  • 55
  • 1
  • 10
    Sorry: This doesn't work cross-platform. Paths assumes the current OS. Your example looks like a Windows path, but it already has linux encodings (which is probably why it worked). If we try a Windows path on a Linux box it won't interpret the directories. Eg Paths.get("c:\\projects\\mystuff").getParent() returns null – Daniel Winterstein Feb 26 '16 at 13:13
2

Do you have the option of using

System.getProperty("file.separator")

to build the string that represents the path?

Adam
  • 21
  • 1
  • Yes, but I didn't want to build the string. Guessing that this must be a pretty common thing I wanted to reuse an existing snippet of code which did the trick. – Grundlefleck Nov 08 '09 at 21:15
  • 1
    If you've used the forward slash everywhere you have a path, this seems to work. `String fileSep = System.getProperty("file.separator"); String path = "path/to/some/file"; path.replaceAll("/", fileSep);` – Adam Nov 09 '09 at 06:28
  • Yeah, Thomas Jung mentioned that in his answer. Was news to me :) – Grundlefleck Nov 09 '09 at 20:43
2

For anyone trying to do this 7 years later, the apache commons separatorsToSystem method has been moved to the FilenameUtils class:

FilenameUtils.separatorsToSystem(String path)
java-addict301
  • 3,220
  • 2
  • 25
  • 37
2

I create this function to check if a String contain a \ character then convert them to /

public static String toUrlPath(String path) {
  return path.indexOf('\\') < 0 ? path : path.replace('\\', '/');
}

public static String toUrlPath(Path path) {
  return toUrlPath(path.toString());
}
Daniel De León
  • 13,196
  • 5
  • 87
  • 72
1
String fileName = Paths.get(fileName).toString();

Works perfectly with Windows at least even with mixed paths, for example

c:\users\username/myproject\myfiles/myfolder

becomes

c:\users\username\myproject\myfiles\myfolder

Sorry haven't check what Linux would make of the above but there again Linux file structure is different so you wouldn't search for such a directory

1

I think there is this hole in Java Paths.

String rootStorePath = Paths.get("c:/projects/mystuff/").toString();

works if you are running it on a system that has the file system you need to use. As pointed out, it used the current OS file system.

I need to work with paths between windows and linux, say to copy a file from one to another. While using "/" every works I guess if you are using all Java commands, but I need to make an sftp call so using / or file.separator etc... does not help me. I cannot use Path() because it converts mine to the default file system I am running on "now".

What Java needs is:
on windows system:

Path posixPath = Paths.get("/home/mystuff", FileSystem.Posix );
stays /home/mystuff/  and does not get converted to \\home\\mystuff

on linux system:
String winPath = Paths.get("c:\home\mystuff", FileSystem.Windows).toString();

stays c:\home\mystuff and does not get converted to /c:/home/mystuff

similar to working with character sets:

URLEncoder.encode( "whatever here", "UTF-8" ).getBytes();

P.S. I also do not want to load a whole apache io jar file to do something simple either. In this case they do not have what I propose anyways.

leonardkraemer
  • 6,573
  • 1
  • 31
  • 54
Stan Towianski
  • 411
  • 2
  • 6
  • 2
    The presented code does not work on any Java version I have tested. If there is a Java version that provides an API used by the code in this answer please specify it. If there is not Java version the code runs in please delete this questions. SO is not for discussing feature wishes. – Robert Jun 08 '20 at 12:51
0

Shouldn't it be enough to say:

"path"+File.Seperator+"to"+File.Seperator+"some"+File.Seperator+"file"
Bakudan
  • 19,134
  • 9
  • 53
  • 73
Dimitri
  • 9
  • 1
  • 1
    As I've mentioned in other comments, yes, I knew a simple string replace strategy would work, but hacking up the string makes the code less readable, and there's a much higher chance for introducing stupid typo errors. Using a solution which was tested, and readable was one of the goals here. – Grundlefleck Nov 08 '09 at 23:47
0

In Scala, the below code helps us to generate a windows-compliant path name from a linux compliant path.

  def osAwareFilename(path: String) : String =  {
    val pathArr = path.split("/")
    if (System.getProperty("os.name").contains("Windows")) {
      pathArr.mkString("\\")
    }
    else path
  }
ForeverLearner
  • 1,901
  • 2
  • 28
  • 51
0

Path implements Iterable<Path> which iterates the name components of the path.

E.g. Path.of(path/to/some/file) iterator() results in [path, to, some, file].

Combine with String.join and you can change the separator:

Path path = Path.of("path/to/some/file");
List<String> nameElements = StreamSupport.stream(path.spliterator(), false).map(Path::toString).toList();

String newSeparator = "\\";
String newPath = String.join(newSeparator, nameElements);

Not the most elegant with the Iterable to Stream conversion, though. It'd be nice if String.join accepted Iterable<Object> and invoked toString for you.

Then it could be written like:

Path path = Path.of("path/to/some/file");

String newSeparator = "\\";
String newPath = String.join(newSeparator, path);
wilmol
  • 1,429
  • 16
  • 22