Writing a file from byte array into a zip file - java

I'm trying to write a file name "content" from a byte array into an existing zip file.
I have managed so far to write a text file \ add a specific file into the same zip.
What I'm trying to do, is the same thing, only instead of a file, a byte array that represents a file. I'm writing this program so it will be able to run on a server, so I can't create a physical file somewhere and add it to the zip, it all must happen in the memory.
This is my code so far without the "writing byte array to file" part.
public static void test(File zip, byte[] toAdd) throws IOException {
Map<String, String> env = new HashMap<>();
env.put("create", "true");
Path path = Paths.get(zip.getPath());
URI uri = URI.create("jar:" + path.toUri());
try (FileSystem fs = FileSystems.newFileSystem(uri, env)) {
Path nf = fs.getPath("avlxdoc/content");
try (BufferedWriter writer = Files.newBufferedWriter(nf, StandardOpenOption.CREATE)) {
//write file from byte[] to the folder
}
}
}
(I tried using the BufferedWriter but it didn't seem to work...)
Thanks!

Don't use a BufferedWriter to write binary content! A Writer is made to write text content.
Use that instead:
final Path zip = file.toPath();
final Map<String, ?> env = Collections.emptyMap();
final URI uri = URI.create("jar:" + zip.toUri());
try (
final FileSystem zipfs = FileSystems.newFileSystem(uri, env);
) {
Files.write(zipfs.getPath("into/zip"), buf,
StandardOpenOption.CREATE, StandardOpenOption.APPEND);
}
(note: APPEND is a guess here; it looks from your question that you want to append if the file already exists; by default the contents will be overwritten)

You should use a ZipOutputStream to access the zipped file.
ZipOutputStream lets you add an entry to the archive from whatever you want, specifying the name of the entry and the bytes of the content.
Provided you have a variable named theByteArray here is a snippet to add an entry to an zip file:
ZipOutputStream zos = new ZipOutputStream(/* either the destination file stream or a byte array stream */);
/* optional commands to seek the end of the archive */
zos.putNextEntry(new ZipEntry("filename_into_the_archive"));
zos.write(theByteArray);
zos.closeEntry();
try {
//close and flush the zip
zos.finish();
zos.flush();
zos.close();
}catch(Exception e){
//handle exceptions
}

Related

Extracting PDF inside a Zip inside a Zip

i have checked everywhere online and stackoverflow and could not find a match specific to this issue.
I am trying to extract a pdf file that is located in a zip file that is inside a zip file (nested zips).
Re-calling the method i am using to extract does not work nor does changing the whole program to accept Inputstreams instead of how i am doing it below.
The .pdf file inside the nested zip is just skipped at this stage
public static void main(String[] args)
{
try
{
//Paths
String basePath = "C:\\Users\\user\\Desktop\\Scan\\";
File lookupDir = new File(basePath + "Data\\");
String doneFolder = basePath + "DoneUnzipping\\";
File[] directoryListing = lookupDir.listFiles();
for (int i = 0; i < directoryListing.length; i++)
{
if (directoryListing[i].isFile()) //there's definately a file
{
//Save the current file's path
String pathOrigFile = directoryListing[i].getAbsolutePath();
Path origFileDone = Paths.get(pathOrigFile);
Path newFileDone = Paths.get(doneFolder + directoryListing[i].getName());
//unzip it
if(directoryListing[i].getName().toUpperCase().endsWith(ZIP_EXTENSION)) //ZIP files
{
unzip(directoryListing[i].getAbsolutePath(), DESTINATION_DIRECTORY + directoryListing[i].getName());
//move to the 'DoneUnzipping' folder
Files.move(origFileDone, newFileDone);
}
}
}
} catch (Exception e)
{
e.printStackTrace(System.out);
}
}
private static void unzip(String zipFilePath, String destDir)
{
//buffer for read and write data to file
byte[] buffer = new byte[BUFFER_SIZE];
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFilePath)))
{
FileInputStream fis = new FileInputStream(zipFilePath);
ZipEntry ze = zis.getNextEntry();
while(ze != null)
{
String fileName = ze.getName();
int index = fileName.lastIndexOf("/");
String newFileName = fileName.substring(index + 1);
File newFile = new File(destDir + File.separator + newFileName);
//Zips inside zips
if(fileName.toUpperCase().endsWith(ZIP_EXTENSION))
{
ZipInputStream innerZip = new ZipInputStream(zis);
ZipEntry innerEntry = null;
while((innerEntry = innerZip.getNextEntry()) != null)
{
System.out.println("The file: " + fileName);
if(fileName.toUpperCase().endsWith("PDF"))
{
FileOutputStream fos = new FileOutputStream(newFile);
int len;
while ((len = innerZip.read(buffer)) > 0)
{
fos.write(buffer, 0, len);
}
fos.close();
}
}
}
//close this ZipEntry
zis.closeEntry(); // java.io.IOException: Stream Closed
ze = zis.getNextEntry();
}
//close last ZipEntry
zis.close();
fis.close();
} catch (IOException e)
{
e.printStackTrace();
}
}
The solution to this is not as obvious as it seems. Despite writing a few zip utilities myself some time ago, getting zip entries from inside another zip file only seems obvious in retrospect
(and I also got the java.io.IOException: Stream Closed on my first attempt).
The Java classes for ZipFile and ZipInputStream really direct your thinking into using the file system, but it is not required.
The functions below will scan a parent-level zip file, and continue scanning until it finds an entry with a specified name. (Nearly) everything is done in-memory.
Naturally, this can be modified to use different search criteria, find multiple file types, etc. and take different actions, but this at least demonstrates the basic technique in question -- zip files inside of zip files -- no guarantees on other aspects of the code, and someone more savvy could most likely improve the style.
final static String ZIP_EXTENSION = ".zip";
public static byte[] getOnePDF() throws IOException
{
final File source = new File("/path/to/MegaData.zip");
final String nameToFind = "FindThisFile.pdf";
final ByteArrayOutputStream mem = new ByteArrayOutputStream();
try (final ZipInputStream in = new ZipInputStream(new BufferedInputStream(new FileInputStream(source))))
{
digIntoContents(in, nameToFind, mem);
}
// Save to disk, if you want
// copy(new ByteArrayInputStream(mem.toByteArray()), new FileOutputStream(new File("/path/to/output.pdf")));
// Otherwise, just return the binary data
return mem.toByteArray();
}
private static void digIntoContents(final ZipInputStream in, final String nameToFind, final ByteArrayOutputStream mem) throws IOException
{
ZipEntry entry;
while (null != (entry = in.getNextEntry()))
{
final String name = entry.getName();
// Found the file we are looking for
if (name.equals(nameToFind))
{
copy(in, mem);
return;
}
// Found another zip file
if (name.toUpperCase().endsWith(ZIP_EXTENSION.toUpperCase()))
{
digIntoContents(new ZipInputStream(new ByteArrayInputStream(getZipEntryFromMemory(in))), nameToFind, mem);
}
}
}
private static byte[] getZipEntryFromMemory(final ZipInputStream in) throws IOException
{
final ByteArrayOutputStream mem = new ByteArrayOutputStream();
copy(in, mem);
return mem.toByteArray();
}
// General purpose, reusable, utility function
// OK for binary data (bad for non-ASCII text, use Reader/Writer instead)
public static void copy(final InputStream from, final OutputStream to) throws IOException
{
final int bufferSize = 4096;
final byte[] buf = new byte[bufferSize];
int len;
while (0 < (len = from.read(buf)))
{
to.write(buf, 0, len);
}
to.flush();
}
Your question asks how to use java (by implication in windows) to extract a pdf from a zip inside another outer zip.
In many systems including windows it is a single line command that will depend on the location of source and target folders, however using the shortest example of current downloads folder it would be in a shell as simple as
tar -xf "german (2).zip" && tar -xf "german.zip" && german.pdf
to shell the command in windows see
How do I execute Windows commands in Java?
The default pdf viewer can open the result so Windows Edge or in my case SumatraPDF
There is generally no point in putting a pdf inside a zip because it cannot be run in there. So single nesting would be advisable if needed for download transportation.
There is no need to add a password to the zip because PDF uses its own password for opening. Thus unwise to add two levels of complexity. Keep it simple.
If you have multiple zips nested inside multiple zips with multiple pdfs in each then you have to be more specific by filtering names. However avoid that extra onion skin where possible.
\Downloads>tar -xf "german (2).zip" "both.zip" && tar -xf "both.zip" "English language.pdf"
You could complicate that by run in a memory or temp folder but it is reliable and simple to use the native file system so consider without Java its fastest to run
CD /D "C:/Users/user/Desktop/Scan/DoneUnzipping" && for %f in (..\Data\*.zip) do tar -xf "%f" "*.zip" && for %f in (*.zip) do tar -xf "%f" "*.pdf" && del "*.zip"
This will extract all inner zips into working folder then extract all PDFs and remove all the essential temporary zips. The source double zips will not be deleted simply touched.
The line that causes your problem looks to be auto-close block you have created when reading the inner zip:
try(ZipInputStream innerZip = new ZipInputStream(fis)) {
...
}
Several likely issues: firstly it is reading the wrong stream - fis not the existing zis.
Secondly, you shouldn't use try-with-resources for auto-close on innerZip as this implicitly calls innerZip.close() when exiting the block. If you view the source code of ZipInputStream via a good IDE you should see (eventually) that ZipInputStream extends InflaterInputStream which itself extends FilterInputStream. A call to innerZip.close() will close the underlying outer stream zis (fis in your case) hence stream is closed when you resume the next entry of the outer zip.
Therefore remove the try() block and add use of zis:
ZipInputStream innerZip = new ZipInputStream(zis);
Use try-catch block only for the outermost file handling:
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFilePath))) {
ZipEntry ze = zis.getNextEntry();
...
}
Thirdly, you appear to be copying the wrong stream when extracting a PDF - use innerZip not outer zis. The code will never extract PDF as these 2 lines can never be true at the same time because a file ending ZIP will never end PDF too:
if(fileName.toUpperCase().endsWith(ZIP_EXTENSION)) {
...
// You want innerEntry.getName() here
if(fileName.toUpperCase().endsWith("PDF"))
You should be able to switch to one line Files.copy and make use of the PDF filename not zip filename:
if(innerEntry.getName().toUpperCase().endsWith("PDF")) {
Path newFile = Paths.get(destDir + '-'+innerEntry.getName().replace("/", "-"));
System.out.println("Files.copy to " + newFile);
Files.copy(innerZip, newFile);
}

Zip4j can't remove or overwrite files inside file

I'm trying to edit the contents of an odt file using zip4j (I tried using java ZipEntries but I couldn't even delete the entries from the file itself that's why I chose to use a library instead). I can confirm that the file I am trying to overwrite exits I can even read from it and tell when it was created so that part works. Now when I'm trying to edit the odt contents (removing or overwriting) Zip4j throws a ZipException which says: cannot rename modified zip file. What am I doing wrong?
try
{
File temp = new File(f.getParent()+"/tmp/content.xml");
new File(temp.getParent()).mkdirs();
FileUtils.write(temp, "", encoding);
net.lingala.zip4j.ZipFile zf = new net.lingala.zip4j.ZipFile(f.getPath());
ZipParameters p = new ZipParameters();
p.setEncryptionMethod(EncryptionMethod.NONE);
p.setOverrideExistingFilesInZip(true);
p.setFileNameInZip("content.xml");
p.setCompressionMethod(CompressionMethod.DEFLATE);
zf.addFile(temp, p);
}
catch (Exception e)
{
e.printStackTrace();
}
The zip file system with its jar:file: protocol is supported by Path & Files. A Path maintains its FileSystem, so one can use all operations.
Path osPath = Paths.get("C:/ ... .odt");
URI uri = URI.create("jar:" + osPath.toUri());
Map<String, String> env = new HashMap<>();
env.put("create", "true");
try (FileSystem zipFS = FileSystems.newFileSystem(uri, env)) {
Files.copy(zipFS.getPath("/media/image1.png"), osPath.resolveSibling("image1.png"),
StandardCopyOption.REPLACE_EXISTING);
Files.move(zipFS.getPath("/media/image2a.png"), zipFS.getPath("/media/image2.png"));
}

How to use FileSystem.getPath() as target for a (File-)Writer?

I have an app that outputs some files.
Depending on a configuration I Need to put them either in a plain Folder or in a zip file.
I'm trying to use FileSystem to decoople the Code actually writing from the actual Destination type.
My problem is that for Paths created by Zip-FileSystems the method .tpFile() is not supported. Therefore I cannot create a FileWriter that I can pass to e.g. JaxB.
public class FileSystemWriteTest {
public static void main(String[] args) throws IOException {
FileSystem localFileSystem = FileSystems.getDefault();
File relativeZipPath = Paths.get("target", "testpath").toFile();
relativeZipPath.mkdirs();
URI relativeZipFilePath = Paths.get(relativeZipPath.toString(), "test.zip").toUri();
URI zipUri = URI.create("jar:"
+ relativeZipFilePath);
System.out.println(zipUri);
Map<String, String> env = new HashMap<>();
env.put("create", "true");
try (FileSystem zipFile = FileSystems.newFileSystem(zipUri, env)) {
for (FileSystem fs : Arrays.asList(localFileSystem, zipFile)) {
Path file = fs.getPath("test.txt");
System.out.println(file.toAbsolutePath());
/* line 31 */ try (FileWriter fileWriter = new FileWriter(file.toFile())) {
fileWriter.write("irgend ein Text zum test\nob das so auch geht");
fileWriter.flush();
}
}
}
}
}
throws
jar:file:///D:/data/scm-workspace/anderes/Test/target/testpath/test.zip
D:\data\scm-workspace\anderes\Test\test.txt
/test.txt
Exception in thread "main" java.lang.UnsupportedOperationException
at com.sun.nio.zipfs.ZipPath.toFile(ZipPath.java:597)
at com.oc.test.filesystem.FileSystemWriteTest.main(FileSystemWriteTest.java:31)
What I try to achief is to mashall the JaxB output directly to disk while it is written, not keeping it in Memory until JaxB finished. (My XMLs are rather big so that I may run into an OOME)
My question:
How can I open a suitable Writer or OutputStream fom a FileSystem backed by a ZIP file?
Alternatively:
What other possibility do I have to hide the real write target (folder vs. ZIP file) from JaxB?
Like most other file system operations, opening an OutputStream, Writer, or Channel can be done via the Files utilities class.
See, for example
Files.newOutputStream(Path, OpenOption...)
Files.newBufferedWriter(Path, Charset, OpenOption...) and
Files.newByteChannel(Path, Set<? extends OpenOption>, FileAttribute...)
but note also the higher-level methods like
Files.write(Path, byte[], OpenOption...)
Files.write(Path, Iterable<? extends CharSequence>, Charset, OpenOption...) and
Files.copy(Path, Path, CopyOption...)
which can copy between different filesystems
But note that for streaming directly into a zip file, using ZipOutputStream atop a FileOutputStream may turn out to be more efficient than using the ZipFileSystem.

Create a file object from a resource path to an image in a jar file

I need to create a File object out of a file path to an image that is contained in a jar file after creating a jar file. If tried using:
URL url = getClass().getResource("/resources/images/image.jpg");
File imageFile = new File(url.toURI());
but it doesn't work. Does anyone know of another way to do it?
To create a file on Android from a resource or raw file I do this:
try{
InputStream inputStream = getResources().openRawResource(R.raw.some_file);
File tempFile = File.createTempFile("pre", "suf");
copyFile(inputStream, new FileOutputStream(tempFile));
// Now some_file is tempFile .. do what you like
} catch (IOException e) {
throw new RuntimeException("Can't create temp file ", e);
}
private void copyFile(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024];
int read;
while((read = in.read(buffer)) != -1){
out.write(buffer, 0, read);
}
}
Don't forget to close your streams etc
This should work.
String imgName = "/resources/images/image.jpg";
InputStream in = getClass().getResourceAsStream(imgName);
ImageIcon img = new ImageIcon(ImageIO.read(in));
Usually, you can't directly get a java.io.File object, since there is no physical file for an entry within a compressed archive. Either you live with a stream (which is best most in the cases, since every good API can work with streams) or you can create a temporary file:
URL imageResource = getClass().getResource("image.gif");
File imageFile = File.createTempFile(
FilenameUtils.getBaseName(imageResource.getFile()),
FilenameUtils.getExtension(imageResource.getFile()));
IOUtils.copy(imageResource.openStream(),
FileUtils.openOutputStream(imageFile));
You cannot create a File object to a reference inside an archive. If you absolutely need a File object, you will need to extract the file to a temporary location first. On the other hand, most good API's will also take an input stream instead, which you can get for a file in an archive.

File Path appearing in the Zipped File

I have a java program as below for zipping a folder as a whole.
public static void zipDir(String dir2zip, ZipOutputStream zos)
{
try
{
File zipDir= new File(dir2zip);
String[] dirList = zipDir.list();
byte[] readBuffer = new byte[2156];
int bytesIn = 0;
for(int i=0; i<dirList.length; i++)
{
File f = new File(zipDir, dirList[i]);
if(f.isDirectory())
{
String filePath = f.getPath();
zipDir(filePath, zos);
continue;
}
FileInputStream fis = new FileInputStream(f);
ZipEntry anEntry = new ZipEntry(f.getPath());
zos.putNextEntry(anEntry);
while((bytesIn = fis.read(readBuffer)) != -1)
{
zos.write(readBuffer, 0, bytesIn);
}
fis.close();
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
public static void main(){
String date=new java.text.SimpleDateFormat("MM-dd-yyyy").format(new java.util.Date());
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("Output/" + date + "_RB" + ".zip"));
zipDir("Output/" + date + "_RB", zos);
zos.close();
}
My query here is. The target folder(+date+_RB) to be zipped is present inside the folder named Output. After successful zipping, when I extract the zipped file, I find a folder Output inside which the (+date+_RB) required folder is present. I need not want that Output folder after the extraction of the zipped file, rather it should directly extract the required folder alone. Please advise on the same.
UPDATE:
I tried Isaac's answer. While extracting the resultant zip file, no folders are getting extracted. Only the files inside all the folders are getting extracted. I just dont need the folder "Output" alone in the resultant zip file. But what the program does is, it doesnt extracts all other folders inside the Output folder, rather it just extracts the files inside those folders. Kindly advise on how to proceed...
It happens because of this:
ZipEntry anEntry = new ZipEntry(f.getPath());
f.getPath() will return Output/ at the beginning of the string. This is due to the flow of your program and how it (mis)uses File objects.
I suggest you construct a File object called, say, tmp:
File tmp = new File(dirList[i]);
The change the construction of f:
File f = new File(zipDir, tmp.getPath());
Then, change this:
ZipEntry anEntry = new ZipEntry(f.getPath());
To this:
ZipEntry anEntry = new ZipEntry(tmp.getPath());
I didn't have time to actually test it, but in a nutshell, your problem is due to how the File object is constructed.

Categories