Creating zip with directory containing special characters - java

I'm trying to create a zip archive with some directories inside. Some of directories has Polish letters in the name like: ą, ę, ł, etc. Everything looks fine except that for any directory with special letter in name there is a another one created in the zip file. What is wrong with the following code:
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) throws URISyntaxException, IOException {
Map<String, String> env = new HashMap<>();
env.put("create", "true");
URI fileUri = new File("zipfs.zip").toPath().toUri();
URI zipUri = new URI("jar:" + fileUri.getScheme(), fileUri.getPath(), null);
try (FileSystem zipfs = FileSystems.newFileSystem(zipUri, env)) {
Path directory = zipfs.getPath("ą");
Files.createDirectory(directory);
Path pathInZipfile = directory.resolve("someFile.txt");
Path source = Paths.get("source.txt");
Files.copy(source, pathInZipfile, StandardCopyOption.REPLACE_EXISTING);
}
FileSystem zipFs = FileSystems.newFileSystem(zipUri, Collections.emptyMap());
Path root = zipFs.getPath("/");
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
#Override
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
System.out.println(path);
return FileVisitResult.CONTINUE;
}
#Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
System.out.println(dir);
return super.preVisitDirectory(dir, attrs);
}
});
}
}
The output of this program is as expected:
/
/ą/
/ą/someFile.txt
But when you open created zip file there are two directories inside:
Ä?
ą
First one is empty and text file is as it should be in the 'ą' directory.

It seems ZipFileSystem doesn't set the Language encoding flag (EFS) with folders. This flag basically says "this path uses UTF-8".
Let's see with zipdetails (skipping not interesting lines):
0072 CENTRAL HEADER #1 02014B50
007A General Purpose Flag 0000 // <= no EFS flag
00A0 Filename 'ą/'
00AC CENTRAL HEADER #2 02014B50
00B4 General Purpose Flag 0800
[Bits 1-2] 0 'Normal Compression'
[Bit 11] 1 'Language Encoding' // <= EFS flag
00DA Filename 'ą/someFile.txt'
Otherwise, ą/ is correctly encoded in UTF-8.
Without this flag, it's up to the program reading/extracting the zip file to choose an encoding (usually the system default). unzip doesn't work well here:
$ unzip -t zipfs.zip
Archive: zipfs.zip
testing: -à/ OK
testing: ą/someFile.txt OK
No errors detected in compressed data of zipfs.zip.
Note, if you disable the unicode support with -UU, you get -à in both entries.
7z works better here (but only because my system default encoding is UTF-8):
$ 7z l zipfs.zip
...
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2017-01-10 22:51:14 D.... 0 0 ą
2017-01-10 22:51:15 ..... 0 2 ą/someFile.txt
------------------- ----- ------------ ------------ ------------------------
2017-01-10 22:51:15 0 2 1 files, 1 folders
If you can't force the way the zip file is opened (if the zip file is sent to users instead of one of your server for example) or only use ASCII characters in your folders, using a different library looks like the only solution.

Related

java.nio.file.InvalidPathException when the path contains colon (:)

I need to construct a path which contains colon (:). Actually the path is not a folder/directory in windows/linux. It is just a path of JCR repo. Below code gives InvalidPathException when the path contains colon
import java.nio.file.Path;
import java.nio.file.Paths;
public class TestCLass {
public static void main(String[] args) {
final Path path = Paths.get("com", "repo:access","resource");
//final Path path = Paths.get("com", "repoaccess","resource"); //Output - com/repoaccess/resource
System.out.println(path);
}
}
Exception
Exception in thread "main" java.nio.file.InvalidPathException: Illegal char <:> at index 8: com\repo:access\resource
at sun.nio.fs.WindowsPathParser.normalize(WindowsPathParser.java:182)
at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:153)
at sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:77)
at sun.nio.fs.WindowsPath.parse(WindowsPath.java:94)
at sun.nio.fs.WindowsFileSystem.getPath(WindowsFileSystem.java:255)
at java.nio.file.Paths.get(Paths.java:84)
Is there any API to access colon(:) and any other special character?
As far as I know, neither Windows nor Linux allow ":" to be put in a file name. That's why Java throws InvalidPathException for such a path/file name. There's no API to work around a restriction imposed by the file system.

Saving file to certain path (Java)

When I run this as a jar, this .properties file is created on the desktop. For the sake of keeping things clean, how can I set the path to save this file somewhere else, like a folder? Or even the jar itself but I was having trouble getting that to work. I plan on giving this to someone and wouldn't want their desktop cluttered in .properties files..
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Properties;
public class DataFile {
public static void main(String[] args) {
Properties prop = new Properties();
OutputStream output = null;
try {
output = new FileOutputStream("config.properties");
prop.setProperty("prop1", "000");
prop.setProperty("prop2", "000");
prop.setProperty("prop3", "000");
prop.store(output, null);
} catch (IOException io) {
io.printStackTrace();
} finally {
if (output != null) {
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
Since you are using the file name without a path, the file you creates ends in the CWD. That is the Current working directory the process inherits from the OS.
That is is you execute your jar file from the Desktop directory any files that use relative paths will end in the Desktop or any of it sub directories.
To control the absolute location of the file you must use absolute paths.
An absolute path always starts with a slash '/'.
Absolute path:
/etc/config.properties
Relative path:
sub_dir/config.properties
The simplest way is to hard code some path into the file path string.
output = new FileOutputStream("/etc/config.properties");
You can of course setup the path in a property which you can pass using the command line instead of hard coding it. The you concat the path name and the file name together.
String path = "/etc";
String full_path = "/etc" + "/" + "config.properties";
output = new FileOutputStream(full_path);
Please note that windows paths in java use a forward slash instead of back slash.
Check this for more details
file path Windows format to java format

How can a renamed file's path remain unchanged in java?

So I finally changed the name of file1 to another name. However what makes me frustrated is that the path remain unchanged!Could you please tell me why and how to deal with it since I always need the handler of file1 for further operation?Here is my sample code:
import java.io.File;
import java.io.IOException;
public class TestFile {
volatile private static File file1;
volatile private static File file2;
public static void main(String[] args) throws IOException {
file1 = new File("D:\\work\\triangle\\src\\original\\test1.java");
file2 = new File("D:\\work\\triangle\\src\\original\\test2.java");
File tmpFile;
String file2name = file2.getAbsolutePath().toString().replace("\\", "/") + ".bak";
System.out.println(file2name);
String file1name = file1.getAbsolutePath().toString()
.replace("\\", "/");
System.out.println(file1name);
tmpFile = new File(file2name);
if (!file1.renameTo(tmpFile)) {
System.err.println("file1->file2name-bak");
}
System.out.println("file1\t"+file1.getAbsolutePath().toString());
System.out.println("tmpFile\t"+tmpFile.getAbsolutePath().toString());
}
}
and I get those output:
D:/work/triangle/src/original/test2.java.bak
D:/work/triangle/src/original/test1.java
file1 D:\work\triangle\src\original\test1.java
tmpFile D:\work\triangle\src\original\test2.java.bak
How can the file1 and tmpFile yield different path?
You are misunderstanding what a File is.
A File denotes a file name / path, not the name / path of a specific file. So, when you use a File to rename a file, the pathname stored in your File object does not change. A File object is immutable.
Then is there any way to change them both?
No. The name / path encoded in a File object does not change, and cannot be changed. If you don't believe me, check the source code that is shipped with your JDK.
(The pathname state of a File is represented by the String-valued path attribute. The only places where path is assigned are the constructors, and the readObject method.)

Java Move File With Certain File Extension

Hi i'm working on a simple program and for the set up of the program i need the program to check a directory for zip files and any zip files in there need to be moved into another folder.
Lets say i have folder1 and it contains 6 zip files and then i have another folder called folder2 that i need all the zips and only the zips in folder1 moved to folder2
Thank you for any help one this problem.
Btw i'm a noob so any code samples would be greatly appreciated
For each file in folder1, use String#endsWith() to see if the file name ends with ".zip". If it does, move it to folder2. FilenameFilter provides a nice way to do this (though it's not strictly necessary).
It would look something like this (not tested):
File f1 = new File("/path/to/folder1");
File f2 = new File("/path/to/folder2");
FilenameFilter filter = new FilenameFilter()
{
#Override public boolean accept(File dir, String name)
{
return name.endsWith(".zip");
}
};
for (File f : f1.listFiles(filter))
{
// TODO move to folder2
}
The pattern of matching "*.zip" in a filesystem is called "file globbing." You can easily select all of these files with a ".zip" file glob using this documentation:
Finding Files. java.nio.file.PathMatcher is what you want. Alternatively, you can list directories as normal and use the name of the file and the ".endsWith()" method of String which will do something similar.
Use a FilenameFilter
String pathToDir = "/some/directory/path";
File myDir = new File(pathToDir);
File[] zipFiles = myDir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(".zip")
}
});
List all file in baseDir, if it ends with '.zip' moves it to destDir
// baseDir = folder1
// destDir = folder2
File[] files = baseDir.listFiles();
for (int i=0; i<files.length; i++){
if (files[i].endsWith(".zip")){
files[i].renameTo(new File(destDir, files[i].getName()));
}
}
API of renameTo
My solution:
import java.io.*;
import javax.swing.*;
public class MovingFile
{
public static void copyStreamToFile() throws IOException
{
FileOutputStream foutOutput = null;
String oldDir = "F:/CAF_UPLOAD_04052011.TXT.zip";
System.out.println(oldDir);
String newDir = "F:/New/CAF_UPLOAD_04052011.TXT.zip.zip"; // name the file in destination
File f = new File(oldDir);
f.renameTo(new File(newDir));
}
public static void main(String[] args) throws IOException
{
copyStreamToFile();
}
}

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