NIO's Filesystems and Paths inconsistent about default FileSystem - java

I am creating a FileSystem to browse the jar in case the access to my reosurces is frim within a jar.
Then I noticed that when creating a new FileSystem, it actually registers as the default file system when using Paths NIO class.
But Filesystems.getDefaultSystem keeps returning the hard disk regular one.
Why is this behaviour inconsistent and so transparent? How can I ask for the Filesystem that Paths is actually using when asked for a relative path as myResources/myResource.txt?
System.out.println("Default FS: "+FileSystems.getDefault().getClass().getName());
URI rscURI = Test.class.getClassLoader().getResource("folder").toURI();
try{ Paths.get(clURI).getFileSystem(); }
catch(FileSystemNotFoundException e){
System.out.println("A new Filesystem for "+clURI.getScheme()+" scheme is created.");
FileSystems.newFileSystem(clURI, Collections.emptyMap());
System.out.println("Default FS: "+FileSystems.getDefault().getClass().getName());
}
return Paths.get(rscURI)

You got the gist of it in your answer; Paths.get() with string arguments is in fact strictly equivalent to FileSystems.getDefault().getPath() with the same string arguments.
Now, as to URIs, it depends on the registered file system providers, and the default filesystem provider always has scheme file. The zip filesystem provider has scheme jar.
Now, if you specify a URI for a registered provider, the provider may, or may not, create the filesystem for you automatically.
Do note however that FileSystem implements Closeable, therefore AutoCloseable; it is therefore recommended that you get a hold of it, and get paths from it, so that you can correctly close it when you're done with it. If you don't, you may leak resources!

Ok, sorry I got it.
Paths.get(URI) and Paths.get(strPath) have different mechanics.
The first one loads unequivocally the specific FS, while the second one uses always getDefault() which seems to be always the disk regular one.
So if were using Paths.get(strPath) then behaviour would be as I was expecting, returning always a reference to disk's file system, coherent with getDefaultFilesystem(), no matter what you registered before,.

Related

Read a file from resources folder JAVA

I want to get a file from the resource file, and to use it in string.
I tried this :
ClassLoader classLoader = getClass().getClassLoader();
File file = new File(classLoader.getResource("/resources/fileC.p12").getFile());
String data = String.valueOf(file);
but doesnt work, thanks for helping
I tried this but i had a error, it returns a null value
new File In java, File means File. As in, an actual file on your actual harddisk. Resources aren't - they are entries in a jarfile and therefore not a file. Simply put, resources cannot be read this way.
Fortunately, File in general is barking up the wrong tree: The correct abstraction is InputStream or similar - that represents 'any stream of bytes'. A file can be an InputStream. So can a network socket, a blob from a network, or, indeed, a resource being streamed to you by the classloader, which could very well be getting it from a network or generating it whole cloth - classloaders can do that. It's an abstract mechanism.
You're also doing it wrong - you want Type.class.getResource. Your way is needlessly wordy and will fail in exotic scenarios (such as bootloaders and agents and the like, which have no classloader).
class Example {
public String getDataFromFileC() throws IOException {
try (var in = Example.class.getResourceAsStream("/resources/fileC.p12")) {
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
}
}
}
This:
Uses getResourceAsStream which gives you an inputstream. As I mentioned, if you mention File, you lose. Hence, we don't.
Uses the proper form: MyType.class.get. This avoids issues when subclassing or in root classloader situations.
MyType.class.get needs a leading slash. the getResource on classloaders requires you not to have it (which explains why your snippet wouldn't work in any scenario - that leading slash).
Uses try-with-resources as you should.
Propagates exceptions as you should.
Configures charset which you should do anytime you go from bytes to strings or vice versa.
NB: Depending on your build system, it may package those resources in the jar as /fileC.p12 and not as /resources/fileC.p12 - in fact, that is likely. You may want to update this to "/fileC.p12".
NB2: String.valueOf(file); does not read files. It just calls toString() on the file object which gives you a path. Resources don't have to be a path so this cannot work. They do have a URL, which may or may not be useful. If you want that: return MyClass.class.getResource("/resources/fileC.p12").toString();.
String data = new String(getClass().getResourceAsStream("/fileC.p12").readAllBytes());
Your resource (which shouldn't be seen as a file as it could and probably should be packaged with your app) is at the root, so useful to start with '/' then it can be addressed from any package. Be cautious with Java >= 17 as that will be decoded by default as UTF-8, so if that's not the encoding, you will have to specify what is in the String ctor. It might be safer to do that anyway.

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.

Converting a Jar-URI into a nio.Path

I'm having trouble coverting from a URI to a nio.Path in the general case. Given a URI with multiple schemas, I wish to create a single nio.Path instance to reflect this URI.
//setup
String jarEmbeddedFilePathString = "jar:file:/C:/Program%20Files%20(x86)/OurSoftware/OurJar_x86_1.0.68.220.jar!/com/our_company/javaFXViewCode.fxml";
URI uri = URI.create(jarEmbeddedFilePathString);
//act
Path nioPath = Paths.get(uri);
//assert --any of these are acceptable
assertThat(nioPath).isEqualTo("C:/Program Files (x86)/OurSoftware/OurJar_x86_1.0.68.220.jar/com/our_company/javaFXViewCode.fxml");
//--or assertThat(nioPath).isEqualTo("/com/our_company/javaFXViewCode.fxml");
//--or assertThat(nioPath).isEqualTo("OurJar_x86_1.0.68.220.jar!/com/our_company/javaFXViewCode.fxml")
//or pretty well any other interpretation of jar'd-uri-to-path any reasonable person would have.
This code currently throws FileSystemNotFoundException on the Paths.get() call.
The actual reason for this conversion is to ask the resulting path about things regarding its package location and file name --so in other words, as long as the resulting path object preserves the ...com/our_company/javaFXViewCode.fxml portion, then its still very convenient for us to use the NIO Path object.
Most of this information is actually used for debugging, so it would not be impossible for me to retrofit our code to avoid use of Paths in this particular instance and instead use URI's or simply strings, but that would involve a bunch of retooling for methods already conveniently provided by the nio.Path object.
I've started digging into the file system provider API and have been confronted with more complexity than I wish to deal with for such a small thing. Is there a simple way to convert from a class-loader provided URI to a path object corresponding to OS-understandable traversal in the case of the URI pointing to a non-jar file, and not-OS-understandable-but-still-useful traversal in the case where the path would point to a resource inside a jar (or for that matter a zip or tarball)?
Thanks for any help
A Java Path belongs to a FileSystem. A file system is implemented by a FileSystemProvider.
Java comes with two file system providers: One for the operating system (e.g. WindowsFileSystemProvider), and one for zip files (ZipFileSystemProvider). These are internal and should not be accessed directly.
To get a Path to a file inside a Jar file, you need to get (create) a FileSystem for the content of the Jar file. You can then get a Path to a file in that file system.
First, you'll need to parse the Jar URL, which is best done using the JarURLConnection:
URL jarEntryURL = new URL("jar:file:/C:/Program%20Files%20(x86)/OurSoftware/OurJar_x86_1.0.68.220.jar!/com/our_company/javaFXViewCode.fxml");
JarURLConnection jarEntryConn = (JarURLConnection) jarEntryURL.openConnection();
URL jarFileURL = jarEntryConn.getJarFileURL(); // file:/C:/Program%20Files%20(x86)/OurSoftware/OurJar_x86_1.0.68.220.jar
String entryName = jarEntryConn.getEntryName(); // com/our_company/javaFXViewCode.fxml
Once you have those, you can create a FileSystem and get a Path to the jar'd file. Remember that FileSystem is an open resource and needs to be closed when you are done with it:
try (FileSystem jarFileSystem = FileSystems.newFileSystem(jarPath, null)) {
Path entryPath = jarFileSystem.getPath(entryName);
System.out.println("entryPath: " + entryPath); // com/our_company/javaFXViewCode.fxml
System.out.println("parent: " + entryPath.getParent()); // com/our_company
}

Identify extension of a file based on its stem name

I have a simple problem that I am quite struggling with. I have several files in a directory and I am reading them and passing processing them based on their type (extension). However, as an input, I receive a path to the file without extension so I have to identify the type myself.
example (files):
files/file1.txt
files/file1.txt
files/pic1.jpg
----------------
String path = "files/file1";
String ext = FilenameUtils.getExtension(path); // this returns null
Is there a way to identify the type of file when the extension is not included in the path?
Your best bet here is to "do it yourself" by implementing instances of FileTypeDetectors.
When you have this, you can then just use Files.probeContentType() to have a string returned which describes the file contents as a MIME type.
The JDK does provide a default implementation but it relies on file extensions, basically; if you have a PNG image named foo.txt, the default implementation will return text/plain where the file is really an image/png.
Which is of course wrong.
Final note: if all you really have is only part of the file name, then use Files.newDirectoryStream() and provide it with the appropriate DirectoryStream.Filter<Path>. Not sure yet why you only have part of it though.
Since you're only given part of the file name, you'll need to search for files that start with that prefix. Note that there could be multiple matches.
Using java.nio.file
Path prefix = Paths.get(path);
Path directory = prefix.getParent();
try (Stream<Path> stream = Files.list(directory)) {
stream.filter(p -> p.getFileName().startsWith(prefix.getFileName() + "."))
.forEach(p -> System.out.printf("Found %s%n", p));
}
Using java.io
File prefix = new File(path);
File directory = prefix.getParentFile();
List<File> matches = directory.listFiles((dir, name) ->
name.startsWith(prefix.getName() + "."));
for (File match: matches) {
System.out.printf("Found %s%n", match);
}
Files.probeContentType(Path) implements a basic MIME type inquiry you can use (or extend), the internal details of which are platform specific. You can also make a little utility method that walks a Set of extensions. A combination of the two approaches may be necessary, depending on your application.
The MIME type checker will give different results on different releases implementations of the JRE. So, always have a fail-over solution.
See: http://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#probeContentType%28java.nio.file.Path
[EDIT]
This actually does not answer the question posited, as this method needs a full, legal Path object to work on. If you are given just the stem name, and the extension is missing, then you neither have an extension to work with nor a valid Path name for Files to work with [and probeContentType() may, in some implementations, just use the extension anyway.]
I'm not sure how you can do this without Path that refers to a real on-disk file that the JRE can access, or by hand if you don't have an extension. If you don't have a File of some sort, you can't even open it up yourself to attempt file type "magic".

Java fails in moving (renaming) a file when the resulting file is on another filesystem

A program we have erred when trying to move files from one directory to another. After much debugging I located the error by writing a small utility program that just moves a file from one directory to another (code below). It turns out that while moving files around on the local filesystem works fine, trying to move a file to another filesystem fails.
Why is this? The question might be platform specific - we are running Linux on ext3, if that matters.
And the second question; should I have been using something else than the renameTo() method of the File class? It seems as if this just works on local filesystems.
Tests (run as root):
touch /tmp/test/afile
java FileMover /tmp/test/afile /root/
The file move was successful
touch /tmp/test/afile
java FileMover /tmp/test/afile /some_other_disk/
The file move was erroneous
Code:
import java.io.File;
public class FileMover {
public static void main(String arguments[] ) throws Exception {
boolean success;
File file = new File(arguments[0]);
File destinationDir = new File(arguments[1]);
File destinationFile = new File(destinationDir,file.getName() );
success = file.renameTo(destinationFile);
System.out.println("The file move was " + (success?"successful":"erroneous"));
}
}
Java 7 and above
Use Files.move(Path source, Path target, CopyOption... opts).
Note that you must not provide the ATOMIC_MOVE option when moving files between file systems.
Java 6 and below
From the docs of File.renameTo:
[...] The rename operation might not be able to move a file from one filesystem to another [...]
The obvious workaround would be to copy the file "manually" by opening a new file, write the content to the file, and delete the old file.
You could also try the FileUtils.moveFile method from Apache Commons.
Javadoc to the rescue:
Many aspects of the behavior of this method are inherently
platform-dependent: The rename operation might not be able to move a
file from one filesystem to another, it might not be atomic, and it
might not succeed if a file with the destination abstract pathname
already exists. The return value should always be checked to make sure
that the rename operation was successful.
Note that the Files class defines the move method to move or rename a
file in a platform independent manner.
From the docs:
Renames the file denoted by this abstract pathname.
Many aspects of the behavior of this method are inherently
platform-dependent: The rename operation might not be able to move a
file from one filesystem to another, it might not be atomic, and it
might not succeed if a file with the destination abstract pathname
already exists. The return value should always be checked to make sure
that the rename operation was successful.
If you want to move file between different file system you can use Apache's moveFile
your ider is error
beause /some_other_disk/ is relative url but completely url ,can not find the url
i have example
java FileMover D:\Eclipse33_workspace_j2ee\test\src\a\a.txt D:\Eclipse33_workspace_j2ee\test\src
The file move was successful
java FileMover D:\Eclipse33_workspace_j2ee\test\src\a\a.txt \Eclipse33_workspace_j2ee\test\src
The file move was erronous
result is url is error

Categories

Resources