I'm extracting a file from a zip archive using this code (omitting all the catch statements and other initialization statements):
zipInputStream = new ZipInputStream(new FileInputStream(file));
zipFile = new ZipFile(file);
for (Enumeration<?> em = zipFile.entries(); em.hasMoreElements();) {
String extractedFileName = em.nextElement().toString();
ZipEntry outerZipEntry = zipInputStream.getNextEntry();
if (outerZipEntry.getName().contains(searchString)) {
extractedFile = new File(outputDir + outerZipEntry.getName());
out = new FileOutputStream(outputDir + extractedFileName);
byte[] buf = new byte[1024];
int len;
while ((len = zipInputStream.read(buf)) > 0) {
out.write(buf, 0, len);
}
break;
}
}
This code works fine when extracting a file in say, /archive.zip/file_i_need.txt.
But when I'm trying to extract a file from /archive.zip/folder1/file_i_need.txt, I get an exception java.lang.NullPointerException when I try to use readLine() to read the file in:
String line = null ;
BufferedReader input = new BufferedReader(newFileReader(extractedFile)) ;
while( (line = input.readLine() ) != null ) {
...
}
I've tested it on both cases and it seems like this code will not work when the file is inside a folder because the extractedFileName is 'folder/file_i_need.txt' compared to just 'file_i_need.txt'.
Any suggestions you can recommend?
Thanks!
extractedFile = new File(outputDir + outerZipEntry.getName());
The problem is you're not taking into account that the entries name may contain a path element, which you are not creating, you simply try an write to the file. Why this doesn't produce an error, I'm not sure.
Are you writing these files on Windows?? This would create a file like folder1/file_i_need.txt on the file system, which is probably invalid at some level :P
Try extracting the file name from the ZipEntry
String name = outerZipEntry.getName();
name = name.substring(name.lastIndexOf("/") + 1);
Obviously, check that name actually contains a "/" first ;)
UPDATE
While I'm at it, this looks wrong
extractedFile = new File(outputDir + outerZipEntry.getName());
out = new FileOutputStream(outputDir + extractedFileName);
Basically your saying outputDir + outerZipEntry.getName() + (outputDir + outerZipEntry.getName())
UPDATE
I tested this on Windows and I get a FileNotFoundException when I try and write the file to a path that does not exist
I also tested it on my MaC and I get a FileNotFoundException
I don't know what your error handling is doing, but it's doing it wrong.
I think your issue is that you can't open a FileOutputStream on line out = new FileOutputStream(outputDir + extractedFileName);. You can't open a stream because if extractedFileName is folder1/file_i_need.txt and outputDir is, for example, C:/OutputDir then you're trying to open a stream on C:/OutputDirfolder1/file_i_need.txt. Such directory doesn't exist and out becomes null.
The post I was referring to in my comment does have a unzip operation, and ther you can see the special handling of directory entries in a zip file.
You are iterating over zip entries in two different ways:
Iteration 1:
for (Enumeration<?> em = zipFile.entries(); em.hasMoreElements();) {
Iteration 2:
ZipEntry outerZipEntry = zipInputStream.getNextEntry();
Just do one or the other. Either use the ZipFile API or the ZipInputStream API. I strongly suspect that is where the NullPointerException is coming from.
Related
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);
}
PROBLEM SOLVED IN EDIT 3
I've been struggling with this problem for sometime. All of the questions here in SO or internet seems to work only on 'shallow' structures with one zip inside of another. However I have zip archive which structure is more or less something like this:
input.zip/
--1.zip/
--folder/
----2.zip/
------3.zip/
--------test/
----------some-other-folder/
----------archive.gz/
------------filte-to-parse
----------file-to-parse3.txt
------file-to-parse.txt
--4.zip/
------folder/
and so on so on, my code needs to handle N-level of zips while preserving original zips, gzips, folders and files structure. Using temporary files is forbidden as of lack of privileges (this is something i'm not willing to change).
This is my code I wrote so far, however ZipOutputStream seems to operate only on one (top) level - in case of directories with files/dirs named exactly the same it throws Exception in thread "main" java.util.zip.ZipException: duplicate entry: folder/. It also skips empty directories (which is not expected). What I want to achieve is somehow move my ZipOutputStream to 'lower' level and do operations on each of zips. Maybe there's better approach to handle all of this problem, any help would be appreciated. I need to perform certain text extraction/modification later, however I'm not starting it yet until reading/writing whole structure is not working properly. Thanks in advance for any help!
//constructor
private final File zipFile;
ArchiveResolver(String fileToHandle) {
this.zipFile = new File(Objects.requireNonNull(getClass().getClassLoader().getResource(fileToHandle)).getFile());
}
void resolveInputFile() throws Exception {
FileInputStream fileInputStream = new FileInputStream(this.zipFile);
FileOutputStream fileOutputStream = new FileOutputStream("out.zip");
ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream);
ZipInputStream zipInputStream = new ZipInputStream(fileInputStream);
zip(zipInputStream, zipOutputStream);
zipInputStream.close();
zipOutputStream.close();
}
// this one doesn't preserve internal structure(empty folders), but can work on each file
private void zip(ZipInputStream zipInputStream, ZipOutputStream zipOutputStream) throws IOException {
ZipEntry entry;
while ((entry = zipInputStream.getNextEntry()) != null) {
System.out.println(entry.getName());
byte[] buffer = new byte[1024];
int length;
if (entry.getName().endsWith(".zip")) {
// wrapping outer zip streams to inner streams making actual entries a new source
ZipInputStream innerZipInputStream = new ZipInputStream(zipInputStream);
ZipOutputStream innerZipOutputStream = new ZipOutputStream(zipOutputStream);
ZipEntry zipEntry = new ZipEntry(entry.getName());
// add new zip entry here to outer zipOutputStream: i.e. data.zip
zipOutputStream.putNextEntry(zipEntry);
// now treat this data.zip as parent and call recursively zipFolder on it
zip(innerZipInputStream, innerZipOutputStream);
// Finish internal stream work when innerZipOutput is done
innerZipOutputStream.finish();
// Close entry
zipOutputStream.closeEntry();
} else if (entry.isDirectory()) {
// putting new zip entry into output stream and adding extra '/' to make
// sure zipOutputStream will treat it as folder
ZipEntry zipEntry = new ZipEntry(entry.getName() + "/");
// this only should preserve internal structure
zipOutputStream.putNextEntry(zipEntry);
// reading everything from zipInputStream
while ((length = zipInputStream.read(buffer)) > 0) {
// sending it straight to zipOutputStream
zipOutputStream.write(buffer, 0, length);
}
zipOutputStream.closeEntry();
// This else will include checking if file is respectively:
// .gz file <- then open it, read from file inside, modify and save it
// .txt file <- also read, modify and preserve
} else {
// create new entry on top of this
ZipEntry zipEntry = new ZipEntry(entry.getName());
zipOutputStream.putNextEntry(zipEntry);
while ((length = zipInputStream.read(buffer)) > 0) {
zipOutputStream.write(buffer, 0, length);
}
zipOutputStream.closeEntry();
}
}
}
// This one preserves internal structure (empty folders and so)
// BUT! no work on each file is possible it just preserves everything as it is
private void zipWhole(ZipInputStream zipInputStream, ZipOutputStream zipOutputStream) throws IOException {
ZipEntry entry;
while ((entry = zipInputStream.getNextEntry()) != null) {
System.out.println(entry.getName());
byte[] buffer = new byte[1024];
int length;
zipOutputStream.putNextEntry(new ZipEntry(entry.getName()));
while ((length = zipInputStream.read(buffer)) > 0) {
zipOutputStream.write(buffer, 0, length);
}
zipOutputStream.closeEntry();
}
}
EDIT:
Updated my code to the newest version, still nothing to be proud of but did some changes however still not working... I've added here two very important comments about (in my opinion) code that fails. So I've tested two approaches - the first one is getting ZipInputStream from zipFile by using getInputStream(ZipEntry e); - throws Exception in thread "main" java.util.zip.ZipException: no current ZIP entry when I'm trying to put some entries to ZipOutputStream. The second approach focuses on "wrapping" ZipInputStream into one another -> this results in empty ZipInputStreams with no entries and application just goes through the files, list them (only top level of zips...) and finishes without saving anything into the out.zip file.
EDIT 2:
With a little suggestions from the people in the comments, I've decided to rewrite my code focusing to close, finish and closeEntry in appropriate places (I hope i did it better now). So right now I've achieved a little of something - code iterates through every entry, and saves it into out.zip file with proper zip packaging inside. Still skips empty folders tho, not sure why (I've checked some of the questions on stack and web, seems ok). Anyway thanks for help so far, I'll try to work this out and I'll keep this updated.
EDIT 3:
After few approaches to the problem and some reading + refactoring I've managed to solve this problem (however there's still problem while running this code on Linux - empty directories are skipped, seems to be connected to they way certain OS preserve file information?).
Here's working solution:
void resolveInputFile() throws IOException {
FileInputStream fileInputStream = new FileInputStream(this.zipFile);
FileOutputStream fileOutputStream = new FileOutputStream("in.zip");
ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream);
ZipInputStream zipInputStream = new ZipInputStream(fileInputStream);
zip(zipInputStream, zipOutputStream);
zipInputStream.close();
zipOutputStream.close();
}
private void zip(ZipInputStream zipInputStream, ZipOutputStream zipOutputStream) throws IOException {
ZipEntry entry;
while ((entry = zipInputStream.getNextEntry()) != null) {
logger.info(entry.getName());
if (entry.getName().endsWith(".zip")) {
// If entry is zip, I create inner zip streams that wrap outer ones
ZipInputStream innerZipInputStream = new ZipInputStream(zipInputStream);
ZipOutputStream innerZipOutputStream = new ZipOutputStream(zipOutputStream);
ZipEntry zipEntry = new ZipEntry(entry.getName());
zipOutputStream.putNextEntry(zipEntry);
zip(innerZipInputStream, innerZipOutputStream);
//As mentioned in comments, proper streams needs to be properly closed/finished, I'm done writing to inner stream so I call finish() rather than close() which closes outer stream
innerZipOutputStream.finish();
zipOutputStream.closeEntry();
} else if (entry.getName().endsWith(".gz")) {
GZIPInputStream gzipInputStream = new GZIPInputStream(zipInputStream);
//small trap while using GZIP - to save it properly I needed to put new ZipEntry to outerZipOutputStream BEFORE creating GZIPOutputStream wrapper
ZipEntry zipEntry = new ZipEntry(entry.getName());
zipOutputStream.putNextEntry(zipEntry);
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(zipOutputStream);
//To make it as as much efficient as possible I've used BufferedReader
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(gzipInputStream));
long start = System.nanoTime();
logger.info("Started to process {}", zipEntry.getName());
String line;
while ((line = bufferedReader.readLine()) != null) {
//PROCESSING LINE BY LINE...
zipOutputStream.write((line + "\n").getBytes());
}
logger.info("Processing of {} took {} miliseconds", entry.getName() ,(System.nanoTime() - start) / 1_000_000);
gzipOutputStream.finish();
zipOutputStream.closeEntry();
} else if (entry.getName().endsWith(".txt")) {
ZipEntry zipEntry = new ZipEntry(entry.getName());
zipOutputStream.putNextEntry(zipEntry);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(zipInputStream));
long start = System.nanoTime();
logger.info("Started to process {}", zipEntry.getName());
String line;
while ((line = bufferedReader.readLine()) != null) {
//PROCESSING LINE BY LINE...
zipOutputStream.write((line + "\n").getBytes());
}
logger.info("Processing of {} took {} miliseconds", entry.getName() ,(System.nanoTime() - start) / 1_000_000);
zipOutputStream.closeEntry();
} else if (entry.isDirectory()) {
//Standard directory preserving
byte[] buffer = new byte[8192];
int length;
// Adding extra "/" to make sure it's dir
ZipEntry zipEntry = new ZipEntry(entry.getName() + "/");
zipOutputStream.putNextEntry(zipEntry);
while ((length = zipInputStream.read(buffer)) > 0) {
// sending it straight to zipOutputStream
zipOutputStream.write(buffer, 0, length);
}
zipOutputStream.closeEntry();
} else {
//In my case it probably will never be called but if there's some different file in here it will be preserved unchanged in the output file
byte[] buffer = new byte[8192];
int length;
ZipEntry zipEntry = new ZipEntry(entry.getName());
zipOutputStream.putNextEntry(zipEntry);
while ((length = zipInputStream.read(buffer)) > 0) {
zipOutputStream.write(buffer, 0, length);
}
zipOutputStream.closeEntry();
}
}
}
Thanks again for all the help and good advices.
There seems to be a lot of debugging and refactoring to be done there.
There's an obvious problem that you are either not closing your streams/entries or doing so in the wrong order. Buffered data will get lost and the central directory not written. (There is a complication that Java streams unhelpfully close the stream they wrap, so there is finish vs close but it still needs to be done in the correct order).
Zip files have no representation for directories as they have a flat structure - the entire file path is included for each entry in both the local header and central directory.
The part of the Java zip library giving a random access interface uses memory mapped files, so you are stuck with streams for everything except, perhaps, the top level.
I am writing a web app that I would like to be able to import a csv file into a database from a jsp. Previously I have used the following code to insert the csv into the database.
LOAD DATA LOCAL INFILE "/myFileLocation.csv"
INTO TABLE myTable
COLUMNS TERMINATED BY ','
OPTIONALLY ENCLOSED BY '"'
ESCAPED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 LINES;
Which works great when I have the file locally.
My question, when I upload the csv file in my jsp as a multipart. Is it possible for me to pass that PartItem file as a variable and replace the "/myFileLocation.csv" with the PartItem files temp location?
I can see the temp location when I debug and view the PartItem file which resides in repository/path under the variables table. Is this at all possible to access or do i need to parse the csv and insert it into the database that way?
I ended up finding a way to make this work. Not sure if it's the best solution but its working as I envisioned. Basically I create a string pointing to an assets folder I created in the web/resources like this.
final String mainPath = this.getServletContext().getRealPath("resources/assets");
Then I read the uploaded file, check to see if the file already exists in my assets folder, if it does I delete it.
Part filePart = request.getPart("csvFile");
String path = mainPath + "/" + filePart.getSubmittedFileName();
File fileTemp = new File(path);
if(fileTemp.exists()){
fileTemp.delete();
}
Lastly I read the uploaded file and write a new file in the location I directed it to which in this case is the assets folder I created like this.
final String fileName = filePart.getSubmittedFileName();
File convFile = new File(filePart.getSubmittedFileName());
FileOutputStream fos = new FileOutputStream(convFile);
OutputStream out = null;
InputStream filecontent= null;
try{
out = new FileOutputStream(new File(mainPath + File.separator + fileName));
filecontent = filePart.getInputStream();
int read = 0;
final byte[] bytes = new byte[1024];
while((read = filecontent.read(bytes)) != -1){
out.write(bytes, 0, read);
}
} catch (FileNotFoundException fne) {
} finally {
if (out != null) {
out.close();
}
if (filecontent != null) {
filecontent.close();
}
}
After that I just passed a string containing the path to the file with the file name to the DAO I created where I was able to utilize the sql statement I had posted above.
Like I stated before, not sure if this is the best way to do this but it seems to be working fine for me and none of my java code is contained within my jsp. If anyone has a better way of doing this or sees something wrong with what I did here let me know, I'd be very interested to hear about it.
I am having a InputStream Object which is actually a zip file. I want to change it back to zip file and save it. I am using DWR's FileTransfer class object to receive the uploaded data from client.
FileTransfer have 3 methods, getInputStream() is one of them. It returns InputStream from FileTransfer object.
In my case, fileTransfer object holds zip file and as well as InputStream object too.
I have done, lot of searches in google. But i am not able to find one example, that illustrates InputStream to zip conversion.
Update
String zipName = file.getName();
String zipType = file.getMimeType();
InputStream zipStream = file.getInputStream();
ZipInputStream zis = new ZipInputStream(zipStream);
System.out.println("File Name: "+zipName+"\n"+"File Type: "+zipType);
int c;
File f2 = new File(DATA_STORE_LOC+dat+".zip");
path.setPath2(DATA_STORE_LOC+dat+".zip");
FileOutputStream fos = new FileOutputStream(f2);
ZipOutputStream zos = new ZipOutputStream(fos);
c = zis.read();
System.out.println(c);
while ((c = zis.read(BUFFER)) != -1) {
zos.write(BUFFER, 0, c);
}
zos.close();
zis.close();
I tried this code, by thought of a typical file copy program. I know it is false, just tried. It gives me java.util.zip.ZipException: ZIP file must have at least one entry.
Any suggestion would be really appreciative!!!!!
See the examples java2s, input and output. If you have more questions feel free to ask them :)
For clarity, in this input example you should do something like:
// FileInputStream fin = new FileInputStream(args[i]);
ZipInputStream zin = new ZipInputStream(ft.getInputStream());
As Don Roby correctly said, if you just want to copy you need not know the file structure and you could use for example static IOUtils.copy(in, out) to copy the file.
Further, if you do wish to extract the ZIP file contents, you should not plainly copy bytes. The ZIP file has a structure, and you extract Entries from the ZIP file, and not just bytes (see the example). Every Entry is a (compressed) file (or the data thereof) with the original name:
ZipEntry ze = null;
while ((ze = zin.getNextEntry()) != null) {
System.out.println("Unzipping " + ze.getName());
FileOutputStream fout = new FileOutputStream(ze.getName());
for (int c = zin.read(); c != -1; c = zin.read()) {
...
Please note the javadoc of getNextEntry():
Reads the next ZIP file entry and positions the stream at the beginning of the entry data.
This positioning is crucial to get to the zipped file contents, and not the metadata.
And I do believe that you accidentally remove the first int:
c = zis.read(); // removing the first
while ((c = zis.read(BUFFER)) != -1) { // so you start with the second?
I believe you mix 2 idioms:
c = zis.read();
while(c != -1) {
...
c = zis.read();
}
and:
int c;
while ((c = zis.read(BUFFER)) != -1) { // so you start with the second?
...
}
I think you can see the difference :)
If your input is a an InputStream from a zip file and your desired output is still a zip file with the same contents, you're just doing a file copy operation and shouldn't have to worry about zip at all. You just need to read from the InputStream and write to a FileOutputStream, more or less as you're doing, but without worrying about wrapping either stream in a zip-aware stream.
ZipInputStream is useful if you have to extract the contents of the zip file as separate files, i.e., to programmatically unzip. And on the other side, ZipOutputStream is used if your have the contents and need to combine them into a zip file.
Use Case
I need to package up our kml which is in a String into a kmz response for a network link in Google Earth. I would like to also wrap up icons and such while I'm at it.
Problem
Using the implementation below I receive errors from both WinZip and Google Earth that the archive is corrupted or that the file cannot be opened respectively. The part that deviates from other examples I'd built this from are the lines where the string is added:
ZipEntry kmlZipEntry = new ZipEntry("doc.kml");
out.putNextEntry(kmlZipEntry);
out.write(kml.getBytes("UTF-8"));
Please point me in the right direction to correctly write the string so that it is in doc.xml in the resulting kmz file. I know how to write the string to a temporary file, but I would very much like to keep the operation in memory for understandability and efficiency.
private static final int BUFFER = 2048;
private static void kmz(OutputStream os, String kml)
{
try{
BufferedInputStream origin = null;
ZipOutputStream out = new ZipOutputStream(os);
out.setMethod(ZipOutputStream.DEFLATED);
byte data[] = new byte[BUFFER];
File f = new File("./icons"); //folder containing icons and such
String files[] = f.list();
if(files != null)
{
for (String file: files) {
LOGGER.info("Adding to KMZ: "+ file);
FileInputStream fi = new FileInputStream(file);
origin = new BufferedInputStream(fi, BUFFER);
ZipEntry entry = new ZipEntry(file);
out.putNextEntry(entry);
int count;
while((count = origin.read(data, 0, BUFFER)) != -1) {
out.write(data, 0, count);
}
origin.close();
}
}
ZipEntry kmlZipEntry = new ZipEntry("doc.kml");
out.putNextEntry(kmlZipEntry);
out.write(kml.getBytes("UTF-8"));
}
catch(Exception e)
{
LOGGER.error("Problem creating kmz file", e);
}
}
Bonus points for showing me how to put the supplementary files from the icons folder into a similar folder within the archive as opposed to at the same layer as the doc.kml.
Update Even when saving the string to a temp file the errors occur. Ugh.
Use Case Note The use case is for use in a web app, but the code to get the list of files won't work there. For details see how-to-access-local-files-on-server-in-jboss-application
You forgot to call close() on ZipOutputStream. Best place to call it is the finally block of the try block where it's been created.
Update: To create a folder, just prepend its name in the entry name.
ZipEntry entry = new ZipEntry("icons/" + file);