Understanding java.nio.file.Path.relativize(Path other) - java

I am trying to familiarize myself with java.nio.file.Path.relativize() to no avail.
I have read the javadocs, and I have seen examples. However, I still cannot get my head around the following example(I use Linux, apologies to window users):
Working directory for program is: /home/userspace/workspace/java8.
With two files: /home/userspace/workspace/java8/zoo.txt and /home/userspace/temp/delete/dictionary.txt
The following program calls Path.relativize():
package certExam.java8.ch9NIO.paths;
import java.nio.file.Path;
import java.nio.file.Paths;
public class Relativize
{
public static void main(String[] args)
{
Path relativePathToZoo = Paths.get("zoo.txt");
Path relativePathToDictionary = Paths.get("../../temp/delete/dictionary.txt");
System.out.println("relativePathToZoo.relativize(relativePathToDictionary): "+relativePathToZoo.relativize(relativePathToDictionary));
System.out.println("relativePathToZoo.relativize(relativePathToDictionary).toAbsolutePath(): "+relativePathToZoo.relativize(relativePathToDictionary).toAbsolutePath());
System.out.println("relativePathToZoo.relativize(relativePathToDictionary).toAbsolutePath().normalize(): "+relativePathToZoo.relativize(relativePathToDictionary).toAbsolutePath().normalize());
System.out.println("relativePathToDictionary.relativize(relativePathToZoo): "+relativePathToDictionary.relativize(relativePathToZoo));
System.out.println("relativePathToDictionary.relativize(relativePathToZoo).toAbsolutePath().normalize(): "+relativePathToDictionary.relativize(relativePathToZoo).toAbsolutePath().normalize());
System.out.println();
}
}
The output is:
relativePathToZoo.relativize(relativePathToDictionary): ../../../temp/delete/dictionary.txt
relativePathToZoo.relativize(relativePathToDictionary).toAbsolutePath(): /home/userspace/workspace/java8/../../../temp/delete/dictionary.txt
relativePathToZoo.relativize(relativePathToDictionary).toAbsolutePath().normalize(): /home/temp/delete/dictionary.txt
relativePathToDictionary.relativize(relativePathToZoo): ../../../../../zoo.txt
relativePathToDictionary.relativize(relativePathToZoo).toAbsolutePath().normalize(): /zoo.txt
My question, the bit I cannot understand is: Why does relativePathToDictionary.relativize(relativePathToZoo) output ../../../../../zoo.txt?
When normalized, it would make you think that zoo.txt lives in the root directory.
How does relativize() work out such a deep path? I understand that relativize() works in relation to the current working directory, so it adds .. to every path. But I am cannot understand, how it worked out the path to zoo.txt in relation to dictionary.txt.

First of all, the current working directory is completely irrelevant. I could reproduce your problem even under Windows, not having any of these files and directories on my system, the only difference being the use of \\ instead of /.
What should relativize do? If you have a path like foo bar baz and ask for relativizing foo bar hello, you’ll get .. hello as that’s the path, relative to foo bar baz to get to foo bar hello, i.e Paths.get("foo", "bar", "baz").resolve(Paths.get("..", "hello")).normalize() produces the same path as Paths.get("foo", "bar", "hello"), regardless of any real file system structure.
Now you ran into the bug JDK-6925169, as suggested by the user Berger in a comment. The Path implementation does not handle . and .. components correctly in relativize, but treats them like any other path component.
So whether you use Paths.get("..", "..", "temp", "delete", "dictionary.txt") or Paths.get("a", "b", "c", "d", "e"), it makes no difference, in either case, the implementation treats it as five nonmatching path components that have to be removed to resolve to Paths.get("zoo.txt"). This applies to both, Windows and Linux. You may verify it with the following platform-independent code:
Path relative = Paths.get("zoo.txt");
Path base1 = Paths.get("..", "..", "temp", "delete", "dictionary.txt");
Path base2 = Paths.get("a", "b", "c", "d", "e");
Path relativized1 = base1.relativize(relative);
System.out.println("relativized1: "+relativized1);
Path relativized2 = base2.relativize(relative);
System.out.println("relativized2: "+relativized2);
Path resolved1 = base1.resolve(relativized1).normalize();
System.out.println("resolved1="+resolved1);
Path resolved2 = base2.resolve(relativized2).normalize();
System.out.println("resolved2="+resolved2);
Since relatize incorrectly treats all component the same, the relativized paths are the same, but since the normalize operation does handle the .. path components, the first resolved path will exhibit the problem whereas the second resolves to the expected zoo.txt.
It might be important for the understanding, that all path components, including dictionary.txt, are treated like directories. The documentation of relativize doesn’t mention that explicitly, but you can derive it from the documented relationship to resolve, whose documentation says “… this method considers this path to be a directory”.

Given the example paths below:
Path p1 = Paths.get("java/temp/zoo.txt");
Path p2 = Paths.get("java/bin/elephant.bin");
Path p1Top2 = p1.relativize(p2);
System.out.println(p1Top2);
We want to get from zoo.txt to elephant.bin in this example.
So, let's start at zoo.txt and ask ourselves: how do I get from zoo.txt to elephant.bin. First I have to go up a directory, so I use ".." Now I'm in temp. (Trace the steps with your finger if it helps!). I have to go up one more to java so, I use ".." again. Now I'm in java. The directory bin is in java, so, I go down to it using "/bin". Once more I go down using "/elephant.bin". We have arrived at our destination.
Put all of the above steps we took together and you get the output:
../../bin/elephant.bin

Related

ServiceLoader cannot find service loaded from path

I have been trying to do a kind of plugin-system using the ServiceLoader. There are 2 modules, the first provides the abstract class LoadedRealmPlugin. The second one extends this class. I have added the file corresponding to the full name of the ServiceProvider and added the service-class to it. IntelliJ does not find any errors (but when changing the filename or classname it does). Here is the structure:
MainModule
src
main
java
com.interestingcompany.mainmodule
LoadedRealmPlugin
MainModule.iml
Plugin
META-INF
services
com.interestingcompany.mainmodule (-> Content: "PluginExtension")
src
PluginExtension
Plugin.iml
(This is simplified, I left out classes that (I think) are not important to the ServiceLoader. I can post a screenshot of the actual structure if anyone needs it)
Here is the code I use to load the Service:
File file = new File("Plugins/Plugin.jar");
URLClassLoader c = new URLClassLoader(new URL[]{file.getAbsoluteFile().toURI().toURL()});
ServiceLoader<LoadedRealmPlugin> loader = ServiceLoader.load(LoadedRealmPlugin.class, c);
LoadedRealmPlugin p = loader.iterator().next(); // Throws a java.util.NoSuchElementException
p.Initialize(RealmPath); // Abstract method implemented in the service
return p;
When trying to run it, I always get an empty ServiceLoader. I looked at this post, but I was not quite sure about how to apply that answer since I am trying to load my plugin from a file. In addition, I found this post. Yet, there was no answer, just some comments that did not seem to have answered the question.
As you might have been able to tell, this is my first time working with classloaders. If there is any additional information needed, just ask me. Thank you for reading through my beginner troubles.
package-less classes are in the unnamed package, which is inaccessible to rather a lot of code, notably including here.
Put PluginExtension.java in a package, make sure the content of your META-INF/services/com.ic.mainmodule file reflects this (content should be pkg.PluginExtension), and it'll work fine.

Incorrect relative path in java

My issue is in the code here (seperated for debugging purposes):
// creating newUniversePane
Scene newUniverseScene = new Scene(newUniversePane, 300, 200);
Class<? extends WorldNoteOrganizerMainController> aClass = getClass();
URL resource = aClass.getResource("../../resources/css/main.css");
String s = resource.toExternalForm();
newUniverseScene.getStylesheets().add(s);
//more code
So, by looking at many examples here on Stack Overflow, it seems that my relative path is correct. However, when I run the program, and it reaches this code, it gives me an error.(I am attempted to add the css to a scene which will be added to a Stage as a popup)
Error message:
Caused by: java.lang.NullPointerException at
controller.WorldNoteOrganizerMainController.handleNewUniverse(WorldNoteOrganizerMainController.java:169)
This error points to the line:
String s = resource.toExternalForm();
which is because resource is null. I have tried many different paths to try to get the file, but have not been successful. Also, I looked around on here, so that I am not repeating a question, but I could not find any questions that would help. I found relative class path questions, but they did not help me fix this. Any help would be greatly appreciated! I have added relative information to the end.
Actual one-liner code:
newUniverseScene.getStylesheets().add(getClass().getResource("/../../resources/css/main.css").toExternalForm();
Paths tried:
URL resource = aClass.getResource("/../resources/css/main.css");
URL resource = aClass.getResource("/resources/css/main.css");
Snapshot of hierarchy:
You need to find out what folder your app is at, when it runs, and how you look it up. Using this.getClass().getResource() the lookup is relative to the location of the class file itself, if the path begins with anything other than "/", When the path begins with "/", it is assumed to be an "absolute" path, and it will start at the "top" of the classpath (which in Eclipse ought to be the "target" folder). So - in you case, you would probably need to use "/css/main.css" (since src/main/resources/* will be copied to target/) - and as pointed out below, "relative paths" do not make sense here.
When you are using relative path, use this:
aClass.getClassLoader().getResource("relative-path");
If you are using Intellij there is an option that gives you the relative path by right clicking on the file.
When you specify the resources folder as a resource folder in Inillj IDEA (the yellow little icon) then call getClass().getResources() it gets you inside the resources directory itself so you just use getClass().getResources("css/main.css")

How to get an instance of sun.nio.fs.UnixFileSystem on a Windows machine?

In particular, I'd like to use the (unfortunately not visible) sun.nio.fs.Globs.toUnixRegexPattern(String glob).
Ok, stepping back and giving a bit of context
I have an iterator of pathes into a remote, unix-like file system (think ssh unixhost find path -type f). I also have a user-supplied glob pattern which I now want to match each path against.
On a unix machine, the following works just fine:
matcher = FileSystems.getDefault().getPathMatcher("glob:" + glob);
// ...
for (String s : remoteFind(...)) {
if (matcher.matches(Paths.get(s))) {
// matches, do something
}
}
But when this is run on Windows, the same program totally fails because the FileSystems.getDefault() returns a Windows filesystem (the horror, the horror) and '\' is used as separator, etc. You get the picture. Nothing matches.
Of course I can stop all this nonsense and just rewrite (or rather, copy) sun.nio.fs.Globs.toUnixRegexPattern(String glob), but is there another, more elegant way?
Ok, so just to close this question, as stated in the comments I ended up writing a method in my FileUtil that is almost verbatim a copy of sun.nio.fs.Globs.toUnixRegexPattern(String glob). Works great.
If somebody finds a better way please add a different answer here.
If you do not make any file system operations locally, you could try to set
-Dfile.separator=/
system variable to mimic the unix path separator. This variable should be passed to JVM on startup
As sun.nio.fs.UnixFileSystem is not even part of my Windows JDK, I went one step back and looked for FileSystemProviders that are available on all platforms. So I found JrtFileSystemProvider, which can be (mis-)used to get a Unix-like path matcher on Windows (the following is copy & paste from some Kotlin code, but you get the idea):
val jrtFileSystem = FileSystems.getFileSystem(URI("jrt:/"))
// ...
val pattern = "..."
val matcher = jrtFileSystem.getPathMatcher("glob:$pattern")
// ...
matcher.matches(jrtFileSystem.getPath("path/to/match"))

CreateProcessW fails (ACESS_DENIED)

I currently convert an application to use CreateProcessW() instead of Runtime.exec() as I need the information it provides. However any call to CreateProcessW() fails with the error code 5 (ACCESS DENIED). I have been unable to find out why this happens as Runtime.exec() runs fine in the same case.
My error could be in one of the following code snippets, the method call and the jna interface.
public ProcessInfo createProcess(String dir, String name){
ProcessInfo pi = new ProcessInfo();
StartupInfo start = new StartupInfo();
mem.CreateProcessW(new WString(name),
null,
null,
null,
false,
0,
null,
new WString(dir),
start.getPointer(),
pi.getPointer());
return pi;
}
My definition of CreateProcessW
boolean CreateProcessW(WString apname,
char[] comline,
Pointer p,
Pointer p2,
boolean inheritHandles,
int createFlags,
String environment,
WString directory,
Pointer startinf,
Pointer processInfo);
Additional Info:
Runtime.exec() succeeds with the given Strings
The size of StartupInfo is set
Testenvironment used: WinXP SP3 and Netbeans 6.9.1
Example parameters used:
Name: moviemk.exe
Dir: C:\Programme\Movie Maker\
Also tested with different paths, so not a whitespace problem
Thanks
Update:
As it turns out the error was caused by my calling code switching around working dir and exe path after I checked them. Because of the resulting access denied I actually thought that it at least found the exe. I will add an IllegalArgumentException to take care of that problem.
Since I had the additional error with the exe being relative to the working dir I will accept that answer. Thanks to all for helping.
CreateProcessW's first parameter has to be either a full path or a path relative to the current directory. It can't be a path relative to the working directory parameter, which seems like what you're expecting it to do.
Try passing C:\Programme\Movie Maker\moviemk.exe as the name parameter
The first parameter lpApplicationName of the CreateProcess function will be used as NULL typically and the second parameter lpCommandLine should contain the command line starting with the program name which you want to start.
Just fry to switch the first and the second parameters which you use currently by the CreateProcessW call.
What is the full path you are entering? Runtime.exec might be quoting the argument internally, and you could be running into this situation:
http://support.microsoft.com/kb/179147
Maybe there is a prefix to the path that exists and is causing it to try to execute a folder or other file?
Try putting quotes around the entire path and see if that helps.

Java reports alias (symlink) as size 0 on Mac OSX. How do I get the true file size?

File file = new File("path to file alias foo");
where "path to file alias foo" is an alias reports file size to be 0 instead of the actual file size. I found a workaround to test for aliases:
public boolean isLink() {
try {
if (file.getAbsolutePath().equals(file.getCanonicalPath())) {
return false;
}
} catch (IOException ex) {
logger.severe(ex.getMessage());
}
return true;
}
EDIT Actually this code does not work, as pointed out by a poster below. I was trying to adapt a solution from a linux symlink example, but I didn't realize that finder aliases and symlinks were not the same.
NOT! this seems to work, but ....
file.getCanonicalFile().length();
still reports file length to be 0. Can anyone point me in the right direction?
Finder aliases are a different beast altogether from normal symbolic links. The *nix tools on OS X are not aware of aliases at all, because they're stored in the resource fork, I believe. If you install osxutils, you can use this shell command to get the target of an alias:
hfsdata -e the-alias
From Java, I don't know of a better way of doing this other than calling Runtime.exec(...).
Also, I just a did a quick check, and your function for detecting aliases does not work. AFAICT, Java is not aware of Finder aliases. If you really want to support them, then you'll either need to use something like osxutils, or use some platform-specific code to read resource forks (will probably involve JNI). Neither option is pretty.
If you go the JNI route, check out the Alias Manager Reference documentation. The relevant functions are FSIsAliasFile and FSResolveAliasFile.
You can use the FileRef Interface from the O'Reilly Java NIO API. I believe the getAttribute() method can handle symbolic links as you want, but I have not tried it on Mac OSX. From the docs:
The options array may be used to
indicate how symbolic links are
handled for the case that the file is
a symbolic link. By default, symbolic
links are followed and the file
attribute of the final target of the
link is read. If the option
NOFOLLOW_LINKS is present then
symbolic links are not followed and so
the method returns the file attribute
of the symbolic link.
size = new File(file.getCanonicalPath()).length();

Categories