Why does Path class allow file within a file? - java

I'm playing around with the NIO Path stuff, and came across this quesion:
What will the following code fragment print?
Path p1 = Paths.get("\\personal\\readme.txt");
Path p2 = Paths.get("\\index.html");
Path p3 = p1.relativize(p2);
System.out.println(p3);
The answer is
..\..\index.html
But this would make the entire Path:
\personal\readme.txt\index.html
This looks like nonsense to me, as you can't put a file within a file like this. Can you?
If readme.txt were a directory instead of a file, I would be perfectly OK with this, but I'm very confused as to why it allows a filepath like this to exist?
Or is there some weird way that you can actually do this?

Both ISOs and Zip files (therefore JAR/WAR/XUL/CHM...) represent files that can contain a folder structure of files. These files can be handled either as a file, or as a folder; both are legitimate uses for them. Therefore, this would be semantically meaningful:
Path p1 = Paths.get("\\personal\\photos.zip");
Path p2 = Paths.get("\\family\\me.png");
Path p3 = p1.relativize(p2);
System.out.println(p3);"
While I am not aware of any implementations in Java that behave this way, it is a semantic used in XUL and Windows Explorer.

The result is ..\..\index.html
From the javadoc:
This method attempts to construct a relative path that when resolved
against this path, yields a path that locates the same file as the
given path. For example, on UNIX, if this path is "/a/b" and the given
path is "/a/b/c/d" then the resulting relative path would be "c/d".
Where this path and the given path do not have a root component, then
a relative path can be constructed.
This means that you would have to go up two folders from this path to reach a path from which you can reach your given path, index.html.
Remember, these are all paths, not actual files/file descriptors.
Path p1 = Paths.get("\\personal\\readme.txt");
Path p2 = Paths.get("\\personal\\index.html");
Path p3 = p1.relativize(p2);
System.out.println(p3);
prints ..\index.html, meaning from \\personal\\readme.txt, go up one and then access index.html.

Related

NIO2 Path Symbols Java

If I created a two paths such as:
Path path3 = Paths.get("E:\\data");
Path path4 = Paths.get("E:\\user\\home");
And then make a new Path(relativePath) by using the relativize() method on the two paths, creating: "..\user\home" does the path symbol(..) in this case refer to "data" or does it just indicate a relative path?
Path relativePath = path3.relativize(path4);
// ..\user\home <- output
So my Question is, what does the Path symbol (..) represent?
The relativize method needs two inputs but doesn't "secretly" encode the base path into it's output, so your relativePath has to be applied to another base path to actually access a path on disk.
But you can apply it to a different base path, e.g. if you want to sync two folder structures below two different base paths.
tl;dr: it just indicates a relative path.
But take care with your path separator: if you hardcode that into your path strings like in your example, it will fail on other systems. Better split up the individual parts in extra strings like this:
Path path4 = Paths.get("E:", "user", "home");

Relativise introducing redundant up-directory ".."

I'm copy files from a jar to a local folder.
The jar is located at a/b.
The folder (in the jar) I'm trying to extract is located at b/c
The destination folder should be z, i.e. a/b/c/x -> z/x
When I use c.relativize("x") I get ../c/x instead of x.
This is a problem when I then try to do: z.resolve(c.relativize(x)).
I get z/../c/x instead of z/x
How do I fix this issue?
I tried z.resolve(c.relativize(x).normalise()) but get the same result
The jar is b in the folder a. c is a resource (folder) inside the jar b. x is a file inside the folder c.
The jar is in folder A/B/
The source folder path inside the jar is /b/c/ with file /b/c/xxx.yyy
The destination folder is Z
The destination file should have path: Z/xxx.yyy
So:
Path sourceZip = Paths.get("/A/B/my.zip");
URI sourceZipURI = URI.create("jar:" + sourceZip.toUri());
Path targetFolder = Paths.get("/Z");
Map<String, Object> senv = new HashMap<>();
try (FileSystem sourceZipFS = FileSystems.newFileSystem(sourceZipURI, senv, null)) {
Path folderInZip = sourceZipFS.getPath("/b/c");
Files.list(folderInZip).forEach(p -> {
Path target = targetFolder.resolve(folderInZip.relativize(p).toString());
try {
Files.createDirectories(target.getParent());
Files.copy(p, target);
} catch (IOException e) {
e.printStackTrace();
}
});
}
The main issue was the reversal of relativize; not the first time I encountered the misunderstanding. child.relativize(parent) gives a .. whereas parent.relativize(child) truncates the path.
To get the target path one needs to convert the source Path to a string, to prevent combining two different file systems.
(I did not take care of subdirectories.)
I think you have misunderstood the usage of path.relativize(Path)
According to https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Path.html#relativize(java.nio.file.Path), there is a statement as below:
Constructs a relative path between this path and a given path.
e.g.
Path pathA = Paths.get("a"); // i.e. /a or /anyDir/anySubDir/a
Path pathB = Paths.get("b"); // i.e. /b or /anyDir/anySubDir/b
pathA.relativize(pathB)
Assume the result is ../b, what does it mean?
It is actually telling you: as you are inside directory a, if you want to access directory b, you need to access by this approach cd ../b.
relativize(Path) is helping to analyzing the relative path between the parent Path and the child Path (i.e. how to access from parent Path to child Path).
Alternatively, from your question, looks like you are preferring to "construct" the path instead of "analyzing" the relative path. relativize(Path) is not the one for this purpose.
So, which one, "construct" manually or "analyze", do you want to achieve?

Velocity's FileResourceLoader can't find resources

I use Velocity in order to load email templates. Those templates are first downloaded from the FTP server and then saved as temporary files.
However, when I try to load the template I get an exception:
org.apache.velocity.exception.ResourceNotFoundException: Unable to find resource 'C:\Users\someUsername\AppData\Local\Temp\template1526050996884865454.html'
And I'm sure the file is there and it's not damaged.
That's how I try to load the template:
template = velocityEngine.getTemplate(tempFile.getCanonicalPath());
Here's the velocity.properties file that I load (and I've checked that the properties are properly initialized!)
file.resource.loader.class=org.apache.velocity.runtime.resource.loader.FileResourceLoader
file.resource.loader=file
file.resource.loader.path=.
So where lies the problem? Is it because AppData folder is hidden by default?
I think there's a design flaw in the Velocity FileResourceLoader. Basically if your file.resource.loader.path is anything other than an empty string, it'll mangle any absolute paths handed to it as the file. Additionally it has Unix/Linux-specific code to "nip off" (paraphrasing the actual code comment) an absolute file-path handed to it (Giving a broken absolute path re-rooted to the current path setting).
Solution 1:
Set the file.resource.loader.path to an empty string (prior to init()) and use absolute file-paths as the file parameter
ve.setProperty("file.resource.loader.path", "");
ve.init();
Template template = ve.getTemplate("C:\\Users\\someUsername\\AppData\\Local\\Temp\\template1526050996884865454.html");
Solution 2: Set the path to be the common root for your temp files and only hand it paths relative to that:
ve.setProperty("file.resource.loader.path", "C:\\Users\\someUsername\\AppData\\Local\\Temp");
ve.init();
Template template = ve.getTemplate("template1526050996884865454.html");
Ultimately I think the FileResourceLoader class would be better if it detected any absolute path handed to it as a file-name and not try to mash the path setting into it.
In addition to #MOles's answer, there is a third solution.
Solution 3: Configure more than one file resource loader: one for absolute resources and one for relative ones. Something like this:
resource.loader=absolute-file, relative-file
absolute-file.resource.loader.class=org.apache.velocity.runtime.resource.loader.FileResourceLoader
absolute-file.resource.loader.path=
relative-file.resource.loader.class=org.apache.velocity.runtime.resource.loader.FileResourceLoader
relative-file.resource.loader.path=.
This will allow files to be loaded either relatively or absolutely, since FileResourceLoader evidently gets confused when you try to use a single instance for either type of path.

Using nio.relativize for a normalized path

Normally, a path ignores all the . (this directory) it contains. So, c:\\personal\\.\\photos\\readme.txt and c:\\personal\\photos\\readme.txt should give identical results for different operations, but in the following code, the normalized path gives a different result. Can anyone explain the reason for this?
Path p1 = Paths.get("c:\\personal\\.\\photos\\readme.txt");
Path p2 = Paths.get("c:\\personal\\index.html");
Path p3 = p1.relativize(p2);
System.out.println(p3);
p1 = p1.normalize();
p2 = Paths.get("c:\\personal\\index.html");
p3 = p1.relativize(p2);
System.out.println(p3);
Output:
..\..\..\index.html
..\..\index.html
Path class itself does not ignore \\. by default. It happens when you explicitly ask through normalize(). Here in oracle documentation on path's relativize method http://docs.oracle.com/javase/7/docs/api/java/nio/file/Path.html#relativize(java.nio.file.Path) For example, if this path is "/a/b" and the given path is "/a/x" then the resulting relative path may be "../x".
So the answer might be that, path does not by default discards \\.. Which along with oracle documentation results the output you see.

java.nio.Path relativize between Paths does assumptions, which I cannot check

I am using the new Path object of java 7 and I am running into an issue.
I have a file storage system with a base directory and I create my own relative path. In the end I want to store just this relative path somewhere. I am running into a problem with Path.relativize though.
I have two usecases.
1.
Path baseDir = Paths.get("uploads");
Path filename = Paths.get("uploads/image/test.png")
return baseDir.relativize(filename);
This returns a Path image/test.png, which is perfect.
However, usecase 2:
Path baseDir = Paths.get("uploads");
Path filename = Paths.get("image/test.png")
return baseDir.relativize(filename);
returns ../image/test.png. I just want it to return "image/test.png"
In the Path tutorial it says
In the absence of any other information, it is assumed that 2 Paths are siblings
What I want is to be able to detect that this is the case. In this case, I want to just return the filename and ignore the baseDir.
I currently solve it like this, but I was hoping there was a better way:
Path rootEnding = getRootDirectory().getName(getRootDirectory().getNameCount() - 1);
for (Path part : path) {
if (part.equals(rootEnding)) {
return getRootDirectory().relativize(path);
}
}
return path;
So my question is, is there any better way of checking this?
Try adding a normalize() after relativize(). It seems to intended to do exactly this (remove unnecessary .. and . ). Don't miss the caution about symlinks in the javadoc.
This isn't 100% equivalent to what you wrote above, but I think it does what you want. Basically, let baseDir be a relative path. Pretend that whatever baseDir is relative to is the root of the file system. Then allow filename to be either relative or absolute from this "simulated root".
What about:
if (filename.startsWith(baseDir)) {
filename = baseDir.relativize(filename);
}

Categories

Resources