I have created a Zip file on a JimFS FileSystem instance. I would now like to read the Zip using the Java FileSystem API.
Here is how I create the FileSystem:
final FileSystem zipFs = FileSystems.newFileSystem(
source, // source is a Path tied to my JimFS FileSystem
null);
However, this throws an error:
java.nio.file.ProviderNotFoundException: Provider not found
Interestingly, the code works with the default FileSystem.
What does this error mean?
How should I create my Zip FileSystem?
This is not supported before JDK 12 via that specific constructor (Path, ClassLoader)
This was fixed in JDK12, with commit 196c20c0d14d99cc08fae64a74c802b061231a41
The offending code was in ZipFileSystemProvider in JDK 11 and earlier:
if (path.getFileSystem() != FileSystems.getDefault()) {
throw new UnsupportedOperationException();
}
This works, but it seems hacky and crucially I'm not sure why it works.
public static FileSystem fileSystemForZip(final Path pathToZip) {
Objects.requireNotNull(pathToZip, "pathToZip is null");
try {
return FileSystems.getFileSystem(pathToZipFile.toUri());
} catch (Exception e) {
try {
return FileSystems.getFileSystem(URI.create("jar:" + pathToZipFile.toUri()));
} catch (Exception e2) {
return FileSystems.newFileSystem(
URI.create("jar:" + pathToZipFile.toUri()),
new HashMap<>());
}
}
}
Check whether source path points to the zip archive file.
In my case it pointed to the ordinary text file which even had other than '.zip' extension.
Related
The Description:
I've created a ZIP file in Java 8 and try to copy a directory with all it's subfiles and directories into this zip file.
Path directory = Paths.get("P:\Java\Test\backups\test.zip");
// path to the world;
Path world = Paths.get("P:\Java\Test\world");
[...]
// Create a map which tells the file system to create a new file if it doesn't exist
ImmutableMap immutableMap = ImmutableMap.of("create", String.valueOf(Files.notExists(this.directory)));
// Get a file system provider which is capable of creating a ZIP file
FileSystemProvider zipProvider = FileSystemProvider.installedProviders().stream()
.filter(provider -> provider.getScheme().equals("jar")).findFirst().get();
// Create the file system
try (FileSystem fs = zipProvider.newFileSystem(this.directory, immutableMap)) {
try {
Files.walk(this.world).forEach((Path sourcePath) -> {
try {
CopyOption[] option = new CopyOption[] {
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES
};
Path destination = this.directory.resolve(this.world.relativize(sourcePath));
Files.copy(sourcePath, destination,option);
} catch (IOException e) {
e.printStackTrace();
}
});
} catch (IOException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
The Problem:
Whenever I add the line Files.copy to copy my directory and all sub-directories and sub-files into the zip file I'm getting the following exception: java.nio.file.AccessDeniedException: .\backups\test.zip
Console output:
In the following stacktrace I changed the line numbers of the class calls to the one's of the code snippet I posted above for better readability except for the call to the ThreadBackup.run method. It is basically the method the code is beeing executed in with some other, but unrelated things.
java.nio.file.AccessDeniedException: .\backups\tests.zip
at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:83)
at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97)
at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102)
at sun.nio.fs.WindowsFileCopy.copy(WindowsFileCopy.java:231)
at sun.nio.fs.WindowsFileSystemProvider.copy(WindowsFileSystemProvider.java:278)
at java.nio.file.Files.copy(Files.java:1274)
at serverutilities.backups.ThreadBackups.lambda$createZipFile$1(ThreadBackups.java:24)
at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.Iterator.forEachRemaining(Iterator.java:116)
at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
at serverutilities.backups.ThreadBackups.createZipFile(ThreadBackups.java:18)
at serverutilities.backups.ThreadBackups.run(ThreadBackups.java:56)
at java.lang.Thread.run(Thread.java:748)
java.nio.file.NoSuchFileException: P:\Java\Test\backups\test.zip
at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:79)
at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:90)
at sun.nio.fs.WindowsLinkSupport.getRealPath(WindowsLinkSupport.java:259)
at sun.nio.fs.WindowsPath.toRealPath(WindowsPath.java:836)
at sun.nio.fs.WindowsPath.toRealPath(WindowsPath.java:44)
at com.sun.nio.zipfs.ZipFileSystemProvider.removeFileSystem(ZipFileSystemProvider.java:322)
at com.sun.nio.zipfs.ZipFileSystem.close(ZipFileSystem.java:305)
at serverutilities.backups.ThreadBackups.createZipFile(ThreadBackups.java:32)
at serverutilities.backups.ThreadBackups.run(ThreadBackups.java:56)
at java.lang.Thread.run(Thread.java:748)
I noticed that whenever I call the Files.copy method the ZIP file isn't even created or atleast not saved, thus the NoSuchFileException is thrown after the AccessDeniedException is thrown for every directory and file I try to copy.
I have never used java.nio.file, but once I had to deal with such task and I used java.util.zip, which is quite straightforward to use just for creating a zip file from a directory
Although, if you can't change what you are using for archiving the directory, then this solution won't be much of a help, but sample code with some explanations:
Create new ZIP archive with new ZipOutputStream
Walk through the file tree which is going to be zipped with Files.walk
For each path of the file tree pack the entries. ZipEntry holds the metadata about a single file in the archive
To use it just call method packDir with Paths of src and the destination.zip
private static void packDir(Path src, Path dest) throws IOException {
try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(dest));
ZipOutputStream zo = new ZipOutputStream(out);
Stream<Path> dirStream = Files.walk(src)) {
dirStream.filter(p -> !p.equals(src)).forEach(path -> {
try {
packEntry(src, zo, path);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
private static void packEntry(Path src, ZipOutputStream zo, Path path) throws IOException {
String name = src.relativize(path).toString().replace('\\', '/');
boolean isDir = Files.isDirectory(path);
if (isDir) {
name += "/";
}
ZipEntry e = new ZipEntry(name);
zo.putNextEntry(e);
if (!isDir) {
Files.copy(path, zo);
}
zo.closeEntry();
}
You are trying to use a regular file as a directory.
In this line
try (FileSystem fs = zipProvider.newFileSystem(this.directory, immutableMap)) {
you are opening or creating a zip file system at this.directory, which must be a valid path within the default file system. After succeeding, this.directory definitely is a regular file (in the zip file format), still within the default file system.
This line
Path destination = this.directory.resolve(this.world.relativize(sourcePath));
is treating this regular file like a directory.
You want to copy into the zip file system, hence you must use a path from the zip file system, not the path to the zip file within the default file system.
You may get the root of the zip filesystem, e.g.
Path zipRoot = fs.getPath("/");
and use this as target. As far as I know, you can’t use the Path retrieved from one file system as argument to a method of the Path of another file system, so you would have to resolve the target path like
Path destination = zipRoot;
for(Path p: this.world.relativize(sourcePath))
destination = destination.resolve(p.toString());
But perhaps there is a simpler method.
Another issue is the use of Files.copy for directories. When the directory already exists (and the root directory always exists), it will fail, unless you specify REPLACE_EXISTING, but this will fail as soon as the target directory is not empty. This simplest solution is to keep existing directories as-is, so the code would look like
try(FileSystem fs = zipProvider.newFileSystem(this.directory, immutableMap)) {
Path zipRoot = fs.getPath("/");
CopyOption[] option = {
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES
};
Files.walk(this.world).forEach(sourcePath -> {
try {
Path destination = zipRoot;
for(Path p: this.world.relativize(sourcePath))
destination = destination.resolve(p.toString());
if(!Files.isDirectory(destination) || !Files.isDirectory(sourcePath))
Files.copy(sourcePath, destination, option);
} catch(IOException e) {
throw new UncheckedIOException(e);
}
});
} catch(IOException|UncheckedIOException e) {
e.printStackTrace(); // TODO replace with actual exception handling
}
This will skip path entries, if the target directory exists and the source also is a directory, as situations where the source is not a directory but the target is an existing directory should be reported via exception.
If you want to enforce the policy of replacing existing files and directories, you would have to implement a tree deletion for the case that there is an existing non-empty directory, but still, you have to skip the root directory, which can’t be deleted.
Some time ago I released some utility classes for adding and extracting files to/from JAR/ ZIP files using the NIO.2 File API.
Here's a snippet from the tutorial:
public void addResource(Path zipPath, Path targetDirPath, Path srcPath, String targetInZipPathString) throws IOException {
Path targetZipPath = copyZipFile(zipPath, targetDirPath);
try (FileSystem jarFS = JarFiles.newJarFileSystem(targetZipPath.toUri())) {
Path targetInZipPath = jarFS.getPath(targetInZipPathString);
// Adds the src directory name to the zip. You can omit this if you just want to copy the contents.
Path finalTargetInZipPath = PathUtils.resolve(targetInZipPath, srcPath.getFileName());
Files.createDirectories(finalTargetInZipPath);
CopyFileVisitor.copy(srcPath, finalTargetInZipPath);
}
}
The CopyFileVisitor uses PathUtils to resolve Paths across FileSystems.
There is a tutorial.
The library is Open Source and available from Maven Central:
<dependency>
<groupId>org.softsmithy.lib</groupId>
<artifactId>softsmithy-lib-core</artifactId>
<version>0.9</version>
</dependency>
I have just read on this tutorial that toRealPath(), should give back the absolute path if the file that the path refers to really exists.
Here is a snippet from the same tutorial:
try {
Path fp = path.toRealPath(); } catch (NoSuchFileException x) {
System.err.format("%s: no such" + " file or directory%n", path);
// Logic for case when file doesn't exist. } catch (IOException x) {
System.err.format("%s%n", x);
// Logic for sort of file error. }
So, now when I use an existing file located on my desktop for example (Path inputPath = Paths.get("/home/user/Desktop/indeed.txt"); It gives me an exception like if it did not exist.
What may cause this problem?
Thanks a lot in advance indeed.
EDIT: I get a NoSuchFileException out of it.
java.nio.file.NoSuchFileException: /home/user/Desktop/indeed.txt
at sun.nio.fs.UnixException.translateToIOException(UnixException.java:86)
at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:102)
at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:107)
at sun.nio.fs.UnixPath.toRealPath(UnixPath.java:833)
at Pathss.main(Pathss.java:25)
according the source of jdk, the translateToIOException method is implemented like this:
private IOException translateToIOException(String file, String other) {
// created with message rather than errno
if (msg != null)
return new IOException(msg);
// handle specific cases
if (errno() == UnixConstants.EACCES)
return new AccessDeniedException(file, other, null);
if (errno() == UnixConstants.ENOENT)
return new NoSuchFileException(file, other, null);
if (errno() == UnixConstants.EEXIST)
return new FileAlreadyExistsException(file, other, null);
// fallback to the more general exception
return new FileSystemException(file, other, errorString());
}
You can view the whole source at here http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/7-b147/sun/nio/fs/UnixException.java#86
According the implementation, when NoSuchFileException is throwed, an ENOENT error occured. ENOENT on unix stands for No such file or directory.
Are you sure file "/home/user/Desktop/indeed.txt" exsits? or you have privileges to access it.
What is the result of command ls -l /home/user/Desktop/indeed.txt
what is the version of jdk you are using?
Can you tell us the exact exception thrown? As tutorial you mentioned says:
This method throws an exception if the file does not exist or cannot be accessed.
So it may be that you simply cannot access that file.
Is there an API to get a classpath resource (e.g. what I'd get from Class.getResource(String)) as a java.nio.file.Path? Ideally, I'd like to use the fancy new Path APIs with classpath resources.
This one works for me:
return Path.of(ClassLoader.getSystemResource(resourceName).toURI());
Guessing that what you want to do, is call Files.lines(...) on a resource that comes from the classpath - possibly from within a jar.
Since Oracle convoluted the notion of when a Path is a Path by not making getResource return a usable path if it resides in a jar file, what you need to do is something like this:
Stream<String> stream = new BufferedReader(new InputStreamReader(ClassLoader.getSystemResourceAsStream("/filename.txt"))).lines();
The most general solution is as follows:
interface IOConsumer<T> {
void accept(T t) throws IOException;
}
public static void processRessource(URI uri, IOConsumer<Path> action) throws IOException{
try {
Path p=Paths.get(uri);
action.accept(p);
}
catch(FileSystemNotFoundException ex) {
try(FileSystem fs = FileSystems.newFileSystem(
uri, Collections.<String,Object>emptyMap())) {
Path p = fs.provider().getPath(uri);
action.accept(p);
}
}
}
The main obstacle is to deal with the two possibilities, either, having an existing filesystem that we should use, but not close (like with file URIs or the Java 9’s module storage), or having to open and thus safely close the filesystem ourselves (like zip/jar files).
Therefore, the solution above encapsulates the actual action in an interface, handles both cases, safely closing afterwards in the second case, and works from Java 7 to Java 18. It probes whether there is already an open filesystem before opening a new one, so it also works in the case that another component of your application has already opened a filesystem for the same zip/jar file.
It can be used in all Java versions named above, e.g. to list the contents of a package (java.lang in the example) as Paths, like this:
processRessource(Object.class.getResource("Object.class").toURI(),new IOConsumer<Path>(){
public void accept(Path path) throws IOException {
try(DirectoryStream<Path> ds = Files.newDirectoryStream(path.getParent())) {
for(Path p: ds)
System.out.println(p);
}
}
});
With Java 8 or newer, you can use lambda expressions or method references to represent the actual action, e.g.
processRessource(Object.class.getResource("Object.class").toURI(), path -> {
try(Stream<Path> stream = Files.list(path.getParent())) {
stream.forEach(System.out::println);
}
});
to do the same.
The final release of Java 9’s module system has broken the above code example. The Java versions from 9 to 12 inconsistently return the path /java.base/java/lang/Object.class for Paths.get(Object.class.getResource("Object.class")) whereas it should be /modules/java.base/java/lang/Object.class. This can be fixed by prepending the missing /modules/ when the parent path is reported as non-existent:
processRessource(Object.class.getResource("Object.class").toURI(), path -> {
Path p = path.getParent();
if(!Files.exists(p))
p = p.resolve("/modules").resolve(p.getRoot().relativize(p));
try(Stream<Path> stream = Files.list(p)) {
stream.forEach(System.out::println);
}
});
Then, it will again work with all versions and storage methods. Starting with JDK 13, this work-around is not necessary anymore.
It turns out you can do this, with the help of the built-in Zip File System provider. However, passing a resource URI directly to Paths.get won't work; instead, one must first create a zip filesystem for the jar URI without the entry name, then refer to the entry in that filesystem:
static Path resourceToPath(URL resource)
throws IOException,
URISyntaxException {
Objects.requireNonNull(resource, "Resource URL cannot be null");
URI uri = resource.toURI();
String scheme = uri.getScheme();
if (scheme.equals("file")) {
return Paths.get(uri);
}
if (!scheme.equals("jar")) {
throw new IllegalArgumentException("Cannot convert to Path: " + uri);
}
String s = uri.toString();
int separator = s.indexOf("!/");
String entryName = s.substring(separator + 2);
URI fileURI = URI.create(s.substring(0, separator));
FileSystem fs = FileSystems.newFileSystem(fileURI,
Collections.<String, Object>emptyMap());
return fs.getPath(entryName);
}
Update:
It’s been rightly pointed out that the above code contains a resource leak, since the code opens a new FileSystem object but never closes it. The best approach is to pass a Consumer-like worker object, much like how Holger’s answer does it. Open the ZipFS FileSystem just long enough for the worker to do whatever it needs to do with the Path (as long as the worker doesn’t try to store the Path object for later use), then close the FileSystem.
I wrote a small helper method to read Paths from your class resources. It is quite handy to use as it only needs a reference of the class you have stored your resources as well as the name of the resource itself.
public static Path getResourcePath(Class<?> resourceClass, String resourceName) throws URISyntaxException {
URL url = resourceClass.getResource(resourceName);
return Paths.get(url.toURI());
}
Read a File from resources folder using NIO, in java8
public static String read(String fileName) {
Path path;
StringBuilder data = new StringBuilder();
Stream<String> lines = null;
try {
path = Paths.get(Thread.currentThread().getContextClassLoader().getResource(fileName).toURI());
lines = Files.lines(path);
} catch (URISyntaxException | IOException e) {
logger.error("Error in reading propertied file " + e);
throw new RuntimeException(e);
}
lines.forEach(line -> data.append(line));
lines.close();
return data.toString();
}
You can not create URI from resources inside of the jar file. You can simply write it to the temp file and then use it (java8):
Path path = File.createTempFile("some", "address").toPath();
Files.copy(ClassLoader.getSystemResourceAsStream("/path/to/resource"), path, StandardCopyOption.REPLACE_EXISTING);
You need to define the Filesystem to read resource from jar file as mentioned in https://docs.oracle.com/javase/8/docs/technotes/guides/io/fsp/zipfilesystemprovider.html. I success to read resource from jar file with below codes:
Map<String, Object> env = new HashMap<>();
try (FileSystem fs = FileSystems.newFileSystem(uri, env)) {
Path path = fs.getPath("/path/myResource");
try (Stream<String> lines = Files.lines(path)) {
....
}
}
I've got a conditional to check if a certain file exists before proceeding (./logs/error.log). If it isn't found I want to create it. However, will
File tmp = new File("logs/error.log");
tmp.createNewFile();
also create logs/ if it doesn't exist?
No.
Use tmp.getParentFile().mkdirs() before you create the file.
File theDir = new File(DirectoryPath);
if (!theDir.exists()) theDir.mkdirs();
File directory = new File(tmp.getParentFile().getAbsolutePath());
directory.mkdirs();
If the directories already exist, nothing will happen, so you don't need any checks.
Java 8 Style
Path path = Paths.get("logs/error.log");
Files.createDirectories(path.getParent());
To write on file
Files.write(path, "Log log".getBytes());
To read
System.out.println(Files.readAllLines(path));
Full example
public class CreateFolderAndWrite {
public static void main(String[] args) {
try {
Path path = Paths.get("logs/error.log");
Files.createDirectories(path.getParent());
Files.write(path, "Log log".getBytes());
System.out.println(Files.readAllLines(path));
} catch (IOException e) {
e.printStackTrace();
}
}
}
StringUtils.touch(/path/filename.ext) will now (>=1.3) also create the directory and file if they don't exist.
No, and if logs does not exist you'll receive java.io.IOException: No such file or directory
Fun fact for android devs: calls the likes of Files.createDirectories() and Paths.get() would work when supporting min api 26.
Using jdk7, I am trying to use the java.nio.file.Files class to move an empty directory, let's say Bar, into another empty directory, let's say Foo
Path source = Paths.get("Bar");
Path target = Paths.get("Foo");
try {
Files.move(
source,
target,
StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
}
After executing that code snippet, I expected that the Bar directory would be in the Foo directory (...\Foo\Bar). Instead it is not. And here's the kicker, it's been deleted as well. Also, no exceptions were thrown.
Am I doing this wrong?
NOTE
I'm looking for a jdk7-specific solution.I am also looking into the problem, but I figured I'd see if there was anyone else playing around with jdk7.
EDIT
In addition to the accepted answer, here's another solution
Path source = Paths.get("Bar");
Path target = Paths.get("Foo");
try {
Files.move(
source,
target.resolve(source.getFileName()),
StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
}
I didn't realize jdk7 java.nio.file.Files is a necessity, so here is the edited solution. Please see if it works coz I have never used the new Files class before.
Path source = Paths.get("Bar");
Path target = Paths.get("Foo", "Bar");
try {
Files.move(
source,
target,
StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
}
In the javadoc for the Files.move method you will find an example where it moves a file into a directory, keeping the same file name. This seems to be what you were looking for.
Here is the solution.
http://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html
suppose we want to move a file to new directory, keeping the same file name, and replacing any existing file of that name in the directory:
Path source = ...
Path newdir = ...
Files.move(source, newdir.resolve(source.getFileName()), REPLACE_EXISTING);
//Files.move(source, newdir.resolve(source.getFileName()), StandardCopyOption.REPLACE_EXISTING);