How to create a file -- including folders -- for a given path? - java

Am downloading a zip file from web. It contain folders and files. Uncompressing them using ZipInputstream and ZipEntry. Zipentry.getName gives the name of file as htm/css/aaa.htm.
So I am creating new File(zipentry.getName);
But problem it is throwing an exception: File not found. I got that it is creating subfolders htm and css.
My question is: how to create a file including its sub directories, by passing above path?

Use this:
File targetFile = new File("foo/bar/phleem.css");
File parent = targetFile.getParentFile();
if (parent != null && !parent.exists() && !parent.mkdirs()) {
throw new IllegalStateException("Couldn't create dir: " + parent);
}
While you can just do file.getParentFile().mkdirs() without checking the result, it's considered a best practice to check for the return value of the operation. Hence the check for an existing directory first and then the check for successful creation (if it didn't exist yet).
Also, if the path doesn't include any parent directory, parent would be null. Check it for robustness.
Reference:
File.getParentFile()
File.exists()
File.mkdir()
File.mkdirs()

You can use Google's guava library to do it in a couple of lines with Files class:
Files.createParentDirs(file);
Files.touch(file);
https://code.google.com/p/guava-libraries/

Java NIO API Files.createDirectories
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Path path = Paths.get("/folder1/folder2/folder3");
Files.createDirectories(path);

You need to create subdirectories if necessary, as you loop through the entries in the zip file.
ZipFile zipFile = new ZipFile(myZipFile);
Enumeration e = zipFile.entries();
while(e.hasMoreElements()){
ZipEntry entry = (ZipEntry)e.nextElement();
File destinationFilePath = new File(entry.getName());
destinationFilePath.getParentFile().mkdirs();
if(!entry.isDirectory()){
//code to uncompress the file
}
}

This is how I do it
static void ensureFoldersExist(File folder) {
if (!folder.exists()) {
if (!folder.mkdirs()) {
ensureFoldersExist(folder.getParentFile());
}
}
}

Looks at the file you use the .mkdirs() method on a File object: http://www.roseindia.net/java/beginners/java-create-directory.shtml
isDirectoryCreated = (new File("../path_for_Directory/Directory_Name")).mkdirs();
if (!isDirectoryCreated)
{
// Directory creation failed
}

Related

Java Copy files into zip AccessDeniedException

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>

Java better way to delete file if exists

We need to call file.exists() before file.delete() before we can delete a file E.g.
File file = ...;
if (file.exists()){
file.delete();
}
Currently in all our project we create a static method in some util class to wrap this code. Is there some other way to achieve the same , so that we not need to copy our utils file in every other project.
Starting from Java 7 you can use deleteIfExists that returns a boolean (or throw an Exception) depending on whether a file was deleted or not. This method may not be atomic with respect to other file system operations. Moreover if a file is in use by JVM/other program then on some operating system it will not be able to remove it. Every file can be converted to path via toPath method . E.g.
File file = ...;
boolean result = Files.deleteIfExists(file.toPath()); //surround it in try catch block
file.delete();
if the file doesn't exist, it will return false.
There's also the Java 7 solution, using the new(ish) Path abstraction:
Path fileToDeletePath = Paths.get("fileToDelete_jdk7.txt");
Files.delete(fileToDeletePath);
Hope this helps.
Apache Commons IO's FileUtils offers FileUtils.deleteQuietly:
Deletes a file, never throwing an exception. If file is a directory, delete it and all sub-directories.
The difference between File.delete() and this method are:
A directory to be deleted does not have to be empty.
No exceptions are thrown when a file or directory cannot be deleted.
This offers a one-liner delete call that won't complain if the file fails to be deleted:
FileUtils.deleteQuietly(new File("test.txt"));
I was working on this type of function, maybe this will interests some of you ...
public boolean deleteFile(File file) throws IOException {
if (file != null) {
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f: files) {
deleteFile(f);
}
}
return Files.deleteIfExists(file.toPath());
}
return false;
}
if you have the file inside a dirrectory called uploads in your project. bellow code can be used.
Path root = Paths.get("uploads");
File existingFile = new File(this.root.resolve("img.png").toUri());
if (existingFile.exists() && existingFile.isFile()) {
existingFile.delete();
}
OR
If it is inside a different directory this solution can be used.
File existingFile = new File("D:\\<path>\\img.png");
if (existingFile.exists() && existingFile.isFile()) {
existingFile.delete();
}
Use the below statement to delete any files:
FileUtils.forceDelete(FilePath);
Note: Use exception handling codes if you want to use.
Use Apache Commons FileUtils.deleteDirectory() or FileUtils.forceDelete() to log exceptions in case of any failures,
or FileUtils.deleteQuietly() if you're not concerned about exceptions thrown.
Generally We create the File object and check if File Exist then delete.
File f1 = new File("answer.txt");
if(f1.exists()) {
f1.delete();
}
OR
File f2 = new File("answer.txt");
f2.deleteOnExit();
If you are uses the Apache Common then below are the option using which you can delete file and directory
File f3 = new File("answer.txt");
FileUtils.deleteDirectory(f3);
This method throws the exception in case of any failure.
OR
File f4 = new File("answer.txt");
FileUtils.deleteQuietly(f4);
This method will not throw any exception.
This is my solution:
File f = new File("file.txt");
if(f.exists() && !f.isDirectory()) {
f.delete();
}
File xx = new File("filename.txt");
if (xx.exists()) {
System.gc();//Added this part
Thread.sleep(2000);////This part gives the Bufferedreaders and the InputStreams time to close Completely
xx.delete();
}

Can I delete a JAR after I accesed its internal file through URL?

In Groovy, I am reading a file from inside a JAR, and after some processing, I wish to delete this JAR, but once accessed through the URL, it doesn't seem to let me.
Example:
File jarFile = new File('jarFile.jar')
URL url = jarFile.toURI().toURL()
URL intUrl = new URL("jar:$url!/internalFile.json")
println intUrl.text // reads text correctly
jarFile.delete() // returns false, cannot delete
The Javadoc of the getText() command says the connection is closed at the end of the call, and this JAR normally isn't on classpath. Is there any way to make this code work?
Try setting the sun.zip.disableMemoryMapping system property:
java -Dsun.zip.disableMemoryMapping=true ....
(or however you set system properties when invoking Gradle). ZipFile (which backs jar: URLs) uses memory mapping by default, and this may be causing Windows to think that the file in question is still open. If this is not an option then you could try using the commons-compress ZipFile implementation instead of the java.util.zip one:
#Grab(group='org.apache.commons', module='commons-compress', version='1.4.1')
import org.apache.commons.compress.archivers.zip.*
File jarFile = new File('jarFile.jar')
ZipFile f = new ZipFile(jarFile)
ZipArchiveEntry json = f.getEntry('internalFile.json')
if(json) {
f.getInputStream(json)?.withStream {
println it.getText('UTF-8')
}
}
f.close()
jarFile.delete()
You could try this:
import java.util.zip.ZipFile
File jarFile = new File( 'jarFile.jar' )
String text = new ZipFile( jarFile ).with { zf ->
String result = zf.entries().findResult { ze ->
if( ze.name == 'internalFile.json' ) {
zf.getInputStream( ze ).withReader {
it.text
}
}
}
zf.close()
result
}
println text
jarFile.delete()
To avoid (what I suspect is) the classloader locking the jar file

How can I access a folder inside of a resource folder from inside my jar File?

I have a resources folder/package in the root of my project, I "don't" want to load a certain File. If I wanted to load a certain File, I would use class.getResourceAsStream and I would be fine!! What I actually want to do is to load a "Folder" within the resources folder, loop on the Files inside that Folder and get a Stream to each file and read in the content... Assume that the File names are not determined before runtime... What should I do? Is there a way to get a list of the files inside a Folder in your jar File?
Notice that the Jar file with the resources is the same jar file from which the code is being run...
Finally, I found the solution:
final String path = "sample/folder";
final File jarFile = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath());
if(jarFile.isFile()) { // Run with JAR file
final JarFile jar = new JarFile(jarFile);
final Enumeration<JarEntry> entries = jar.entries(); //gives ALL entries in jar
while(entries.hasMoreElements()) {
final String name = entries.nextElement().getName();
if (name.startsWith(path + "/")) { //filter according to the path
System.out.println(name);
}
}
jar.close();
} else { // Run with IDE
final URL url = Launcher.class.getResource("/" + path);
if (url != null) {
try {
final File apps = new File(url.toURI());
for (File app : apps.listFiles()) {
System.out.println(app);
}
} catch (URISyntaxException ex) {
// never happens
}
}
}
The second block just work when you run the application on IDE (not with jar file), You can remove it if you don't like that.
Try the following.
Make the resource path "<PathRelativeToThisClassFile>/<ResourceDirectory>" E.g. if your class path is com.abc.package.MyClass and your resoure files are within src/com/abc/package/resources/:
URL url = MyClass.class.getResource("resources/");
if (url == null) {
// error - missing folder
} else {
File dir = new File(url.toURI());
for (File nextFile : dir.listFiles()) {
// Do something with nextFile
}
}
You can also use
URL url = MyClass.class.getResource("/com/abc/package/resources/");
The following code returns the wanted "folder" as Path regardless of if it is inside a jar or not.
private Path getFolderPath() throws URISyntaxException, IOException {
URI uri = getClass().getClassLoader().getResource("folder").toURI();
if ("jar".equals(uri.getScheme())) {
FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap(), null);
return fileSystem.getPath("path/to/folder/inside/jar");
} else {
return Paths.get(uri);
}
}
Requires java 7+.
I know this is many years ago . But just for other people come across this topic.
What you could do is to use getResourceAsStream() method with the directory path, and the input Stream will have all the files name from that dir. After that you can concat the dir path with each file name and call getResourceAsStream for each file in a loop.
I had the same problem at hands while i was attempting to load some hadoop configurations from resources packed in the jar... on both the IDE and on jar (release version).
I found java.nio.file.DirectoryStream to work the best to iterate over directory contents over both local filesystem and jar.
String fooFolder = "/foo/folder";
....
ClassLoader classLoader = foofClass.class.getClassLoader();
try {
uri = classLoader.getResource(fooFolder).toURI();
} catch (URISyntaxException e) {
throw new FooException(e.getMessage());
} catch (NullPointerException e){
throw new FooException(e.getMessage());
}
if(uri == null){
throw new FooException("something is wrong directory or files missing");
}
/** i want to know if i am inside the jar or working on the IDE*/
if(uri.getScheme().contains("jar")){
/** jar case */
try{
URL jar = FooClass.class.getProtectionDomain().getCodeSource().getLocation();
//jar.toString() begins with file:
//i want to trim it out...
Path jarFile = Paths.get(jar.toString().substring("file:".length()));
FileSystem fs = FileSystems.newFileSystem(jarFile, null);
DirectoryStream<Path> directoryStream = Files.newDirectoryStream(fs.getPath(fooFolder));
for(Path p: directoryStream){
InputStream is = FooClass.class.getResourceAsStream(p.toString()) ;
performFooOverInputStream(is);
/** your logic here **/
}
}catch(IOException e) {
throw new FooException(e.getMessage());
}
}
else{
/** IDE case */
Path path = Paths.get(uri);
try {
DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path);
for(Path p : directoryStream){
InputStream is = new FileInputStream(p.toFile());
performFooOverInputStream(is);
}
} catch (IOException _e) {
throw new FooException(_e.getMessage());
}
}
Another solution, you can do it using ResourceLoader like this:
import org.springframework.core.io.Resource;
import org.apache.commons.io.FileUtils;
#Autowire
private ResourceLoader resourceLoader;
...
Resource resource = resourceLoader.getResource("classpath:/path/to/you/dir");
File file = resource.getFile();
Iterator<File> fi = FileUtils.iterateFiles(file, null, true);
while(fi.hasNext()) {
load(fi.next())
}
If you are using Spring you can use org.springframework.core.io.support.PathMatchingResourcePatternResolver and deal with Resource objects rather than files. This works when running inside and outside of a Jar file.
PathMatchingResourcePatternResolver r = new PathMatchingResourcePatternResolver();
Resource[] resources = r.getResources("/myfolder/*");
Then you can access the data using getInputStream and the filename from getFilename.
Note that it will still fail if you try to use the getFile while running from a Jar.
As the other answers point out, once the resources are inside a jar file, things get really ugly. In our case, this solution:
https://stackoverflow.com/a/13227570/516188
works very well in the tests (since when the tests are run the code is not packed in a jar file), but doesn't work when the app actually runs normally. So what I've done is... I hardcode the list of the files in the app, but I have a test which reads the actual list from disk (can do it since that works in tests) and fails if the actual list doesn't match with the list the app returns.
That way I have simple code in my app (no tricks), and I'm sure I didn't forget to add a new entry in the list thanks to the test.
Below code gets .yaml files from a custom resource directory.
ClassLoader classLoader = this.getClass().getClassLoader();
URI uri = classLoader.getResource(directoryPath).toURI();
if("jar".equalsIgnoreCase(uri.getScheme())){
Pattern pattern = Pattern.compile("^.+" +"/classes/" + directoryPath + "/.+.yaml$");
log.debug("pattern {} ", pattern.pattern());
ApplicationHome home = new ApplicationHome(SomeApplication.class);
JarFile file = new JarFile(home.getSource());
Enumeration<JarEntry> jarEntries = file.entries() ;
while(jarEntries.hasMoreElements()){
JarEntry entry = jarEntries.nextElement();
Matcher matcher = pattern.matcher(entry.getName());
if(matcher.find()){
InputStream in =
file.getInputStream(entry);
//work on the stream
}
}
}else{
//When Spring boot application executed through Non-Jar strategy like through IDE or as a War.
String path = uri.getPath();
File[] files = new File(path).listFiles();
for(File file: files){
if(file != null){
try {
InputStream is = new FileInputStream(file);
//work on stream
} catch (Exception e) {
log.error("Exception while parsing file yaml file {} : {} " , file.getAbsolutePath(), e.getMessage());
}
}else{
log.warn("File Object is null while parsing yaml file");
}
}
}
Took me 2-3 days to get this working, in order to have the same url that work for both Jar or in local, the url (or path) needs to be a relative path from the repository root.
..meaning, the location of your file or folder from your src folder.
could be "/main/resources/your-folder/" or "/client/notes/somefile.md"
Whatever it is, in order for your JAR file to find it, the url must be a relative path from the repository root.
it must be "src/main/resources/your-folder/" or "src/client/notes/somefile.md"
Now you get the drill, and luckily for Intellij Idea users, you can get the correct path with a right-click on the folder or file -> copy Path/Reference.. -> Path From Repository Root (this is it)
Last, paste it and do your thing.
Simple ... use OSGi. In OSGi you can iterate over your Bundle's entries with findEntries and findPaths.
Inside my jar file I had a folder called Upload, this folder had three other text files inside it and I needed to have an exactly the same folder and files outside of the jar file, I used the code below:
URL inputUrl = getClass().getResource("/upload/blabla1.txt");
File dest1 = new File("upload/blabla1.txt");
FileUtils.copyURLToFile(inputUrl, dest1);
URL inputUrl2 = getClass().getResource("/upload/blabla2.txt");
File dest2 = new File("upload/blabla2.txt");
FileUtils.copyURLToFile(inputUrl2, dest2);
URL inputUrl3 = getClass().getResource("/upload/blabla3.txt");
File dest3 = new File("upload/Bblabla3.txt");
FileUtils.copyURLToFile(inputUrl3, dest3);

Renaming a File/Folder inside a Zip File in Java?

I have a zip file containing a folder structure like
main-folder/
subFolder1/
subFolder2/
subFolder3/
file3.1
file3.2
I would like to rename folder main-folder to let's say versionXY inside that very zip file using Java.
Is there a simpler way than extracting the whole zip file and recreating a new one using the new folder names?
Zip is an archive format, so mutating generally involves rewriting the file.
Some particular features of zip also get in the way (zip is full of "features"). As well as the central directory at the end of the archive, each component file is preceded by its file name. Zip doesn't have a concept of directories - file names are just strings that happen to include "/" characters (and substrings such as "../".
So, you really need to copy the file using ZipInputStream and ZipOutputStream, renaming as you go. If you really wanted to you could rewrite the file in place doing your own buffering. The process does cause the contents to be recompressed as the standard API has no means of obtaining the data in compressed form.
Edit: #Doval points out that #megasega's answer uses Zip File System Provider in NIO, new (relative to this answer) in Java SE 7. It's performance will likely be not great, as were the archive file systems in RISC OS' GUI of thirty years ago.
I think you'll be able to find help for this task using the Commons Compress, especially ZipArchiveEntry
I know you asked about Java but just for archival purposes I thought I would contribute a note about .NET.
DotNetZip is a .NET library for zip files that allows renaming of entries. As Tom Hawtin's reply states, directories are not first-class entities in the zip file metadata, and as a result, no zip libraries that I know of expose a "rename directory" verb. But some libraries allow you to rename all the entries that have names that indicate a particular directory, which gives you the result you want.
In DotNetZip, it would look like this:
var regex = new Regex("/OldDirName/.*$");
int renameCount= 0;
using (ZipFile zip = ZipFile.Read(ExistingZipFile))
{
foreach (ZipEntry e in zip)
{
if (regex.IsMatch(e.FileName))
{
// rename here
e.FileName = e.FileName.Replace("/OldDirName/", "/NewDirName/");
renameCount++;
}
}
if (renameCount > 0)
{
zip.Comment = String.Format("This archive has been modified. {0} entries have been renamed.", renameCount);
// any changes to the entries are made permanent by Save()
zip.Save(); // could also save to a new zip file here
}
}
You can also add or remove entries, inside the using clause.
If you save to the same file, then DotNetZip rewrites only the changed metadata - the entry headers and the central directory records for renamed entries, which saves time with large archives. If you save to a new file or stream, then all of the zip data gets written.
This is doing the trick. Blazing fast since it works only on the central directory and not the files.
// rezip( zipfile, "/main-folder", "/versionXY" );
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
protected void rezip( String zipfile, String olddir, String newdir ) {
Path zipFilePath = Paths.get( zipfile );
try (FileSystem fs = FileSystems.newFileSystem( zipFilePath, null )) {
Path oldpathInsideZipPath = fs.getPath( olddir );
if( ! Files.exists( Paths.get( newdir ) ) )
Files.createDirectory( Paths.get( newdir ) );
if ( Files.exists( oldpathInsideZipPath, LinkOption.NOFOLLOW_LINKS ) ) {
Files.walkFileTree(oldpathInsideZipPath, new SimpleFileVisitor<Path>() {
#Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException
{
if( file.toString().indexOf( olddir ) > -1 ){
String a = file.toString().replaceAll( olddir, newdir );
Path b = fs.getPath( a );
if( ! Files.exists( b.getParent() ) ){
Files.createDirectories( b.getParent() );
}
Files.move( file, b, LinkOption.NOFOLLOW_LINKS );
}
return FileVisitResult.CONTINUE;
}
#Override
public FileVisitResult postVisitDirectory(Path dir, IOException e)
throws IOException
{
if (e == null) {
Files.delete(dir);
return FileVisitResult.CONTINUE;
} else {
// directory iteration failed
throw e;
}
}
});
}
fs.close();
} catch ( Exception e ) {
e.printStackTrace();
}
}

Categories

Resources