Previously we had some zip files within our web application. We would want to pares a specific text document within the zip file. This wasn't a problem:
URL url = getClass().getResource(zipfile);
ZipFile zip = new ZipFile(url.getFile().replaceAll("%20", " "));
Entry entry = zip.getEntry("file.txt");
InputStream is = zip.getInputStream(entry);
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line = reader.readLine();
while (line != null) {
// do stuff
}
However we've moved these zip files into another module and want to package them within a jar. Unfortunately, creating the ZipFile now fails. I can get an InputStream for the zip: but I have no way of getting an input stream for the entry itself.
InputStream is = getClass().getResourceAsStream(zipfile);
ZipInputStream zis = new ZipInputStream(is);
ZipEntry entry = zis.getNextEntry();
while (entry != null && !entry.getName().equals("file.txt")) {
entry = zis.getNextEntry();
}
but I have no way of getting an input stream for the entry itself. I tried finding the length of the entry and getting the next n bytes from the ZipInputStream but this didn't work for me. It seemed all bytes read were 0.
Is there a way round this or am I going to have to move the zip files back into the core project?
How about TrueZip? Using it you could simply open the zipped file as if it was located inside a directory.
new FileOutputStream("/path/to/some-jar.jar/internal/zip/file.zip/myfile.txt");
According to the docs, infinite nesting is also supported. I have yet to actually use this project, but it's been on my radar for a while and it seems applicable to your problem.
Project Site: http://truezip.java.net/ (edited)
entry can give you the inputstream of the inner-zip file.
InputStream innerzipstream = zip.getInputStream(entry);
So you can use
new ZipInputStream(innerzipstream);
and ask the ZipInputStream to retrieve the content of the inner-zip-file (in an ordered fashion, you don't have random access because it's a ZipInputStream)
Look at http://download.oracle.com/javase/1.4.2/docs/api/java/util/zip/ZipInputStream.html
Sequential zip access
As ZipInputStream is reading a zip file from an input stream it has to do things in order:
// DO THIS for each entry
ZipEntry e = zipInputStreamObj.getNextEntry();
e.getName // and all data
int size = e.getSize(); // the byte count
while (size > 0) {
size -= zipInputStreamObj.read(...);
}
zipInputStreamObj.closeEntry();
// DO THIS END
zipInputStreamObj.close();
Note: I don't know if ZipInputStream.getNextEntry() returns null when end of zip file is reached or not. I hope so because I don't know other way to realize when there are no more entries.
I have modified the Sequential Zip access code provided above:
File destFile = new File(destDir, jarName);
JarOutputStream jos = new JarOutputStream(new FileOutputStream(destFile));
JarInputStream jis = new JarInputStream(is);
JarEntry jarEntry = jis.getNextJarEntry();
for (; jarEntry != null ; jarEntry = jis.getNextJarEntry()) {
jos.putNextEntry(new JarEntry(jarEntry.getName()));
if(jarEntry.isDirectory()) {
continue;
}
int bytesRead = jis.read(buffer);
while(bytesRead != -1) {
jos.write(buffer, 0, bytesRead);
bytesRead = jis.read(buffer);
}
}
is.close();
jis.close();
jos.flush();
jos.closeEntry();
jos.close();
In the above code, I am trying to copy a Jar file inside another Jar file to a folder in file system.
'is' is the input stream to the jar file inside another jar file (jar.getInputStream("lib/abcd.jar"))
it is also possible to parse the string and open an ZipInputStream on another ZipInputStream and set the entry to the file inside.
e.g. you have the String like above "path/to/some-jar.jar/internal/zip/file.zip/myfile.txt"
private static final String[] zipFiles = new String[] { ".zip", ".jar" };
public static InputStream getResourceAsStream(final String ref) throws IOException {
String abstractPath = ref.replace("\\", "/");
if (abstractPath.startsWith("/")) {
abstractPath = abstractPath.substring(1);
}
final String[] pathElements = abstractPath.split("/");
return getResourceAsStream(null, pathElements);
}
private static InputStream getResourceAsStream(final ZipInputStream parentStream, final String[] pathElements)
throws IOException {
if (pathElements.length == 0) return parentStream;
final StringBuilder nextFile = new StringBuilder();
for (int index = 0; index < pathElements.length; index++) {
final String pathElement = pathElements[index];
nextFile.append((index > 0 ? "/" : "") + pathElement);
if (pathElement.contains(".")) {
final String path = nextFile.toString();
if (checkForZip(pathElement)) {
final String[] restPath = new String[pathElements.length - index - 1];
System.arraycopy(pathElements, index + 1, restPath, 0, restPath.length);
if (parentStream != null) {
setZipToEntry(parentStream, path);
return getResourceAsStream(new ZipInputStream(parentStream), restPath);
} else return getResourceAsStream(new ZipInputStream(new FileInputStream(path)), restPath);
} else {
if (parentStream != null) {
setZipToEntry(parentStream, path);
return parentStream;
} else return new FileInputStream(path);
}
}
}
throw new FileNotFoundException("File not found: " + nextFile.toString());
}
private static void setZipToEntry(final ZipInputStream in, final String name) throws IOException {
ZipEntry entry;
while ((entry = in.getNextEntry()) != null) {
if (entry.getName().equals(name)) return;
}
throw new FileNotFoundException("File not found: " + name);
}
private static boolean checkForZip(final String ref) {
for (final String zipFile : zipFiles) {
if (ref.endsWith(zipFile)) return true;
}
return false;
}
Related
I am trying to unzip specific files from a zip. I first get a ZipInputStream:
ZipInputStream zipIn = new ZipInputStream(new BufferedInputStream(new FileInputStream(filePath)));
Alright, this works! Now I want to extract two files, called F1 and F2, so I call
extractFileFromZip(zipIn, Path + "F1", "F1")
extractFileFromZip(zipIn, Path + "F2", "F2")
public static boolean extractFileFromZip(ZipInputStream inZip, String file, String name) throws Exception {
byte data[] = new byte[BUFFER_SIZE];
boolean found = false;
ZipEntry ze;
while((ze = inZip.getNextEntry()) != null) {
if(ze.getName().equals(name)) {
found = true;
// delete old file first
File oldFile = new File(file);
if(oldFile.exists()) {
if(!oldFile.delete()) {
throw new Exception("Could not delete " + file);
}
}
FileOutputStream outFile = new FileOutputStream(file);
int count = 0;
while((count = inZip.read(data)) != -1) {
outFile.write(data, 0, count);
}
outFile.close();
//inZip.closeEntry();
}
}
return true;
}
Now the problem is with inZip.getNextEntry(). For F1 it will cycle through all the files correctly and then give null. But for F2, it will just give null.
Why does this happen?
You're scanning the entire stream, consuming it. When you try to do it the second time, the stream is already at-end, so will do nothing.
Also, streaming all bytes in the zip file is slow, if you only want a small part of it.
Use a ZipFile instead, since it allows random access to the zip entries, so it is faster, and it allows reading the entries in random order.
Note: The code below has been changed to use Java 7+ features for better error handling, such as try-with-resources and NIO.2.
ZipFile zipFile = new ZipFile(filePath);
extractFileFromZip(zipFile, path + "F1", "F1");
extractFileFromZip(zipFile, path + "F2", "F2");
public static boolean extractFileFromZip(ZipFile zipFile, String file, String name) throws IOException {
ZipEntry ze = zipFile.getEntry(name);
if (ze == null)
return false;
Path path = Paths.get(file);
Files.deleteIfExists(path);
try (InputStream in = zipFile.getInputStream(ze)) {
Files.copy(in, path);
}
return true;
}
Alternatively, stream it only once, then check for both names in the while loop.
Map<String, String> nameMap = new HashMap<>();
nameMap.put("F1", path + "F1");
nameMap.put("F2", path + "F2");
extractFilesFromZip(filePath, nameMap);
public static void extractFilesFromZip(String filePath, Map<String, String> nameMap) throws IOException {
try (ZipInputStream zipIn = new ZipInputStream(new BufferedInputStream(new FileInputStream(filePath)))) {
for (ZipEntry ze; (ze = zipIn.getNextEntry()) != null; ) {
String file = nameMap.get(ze.getName());
if (file != null) {
Path path = Paths.get(file);
Files.deleteIfExists(path);
Files.copy(zipIn, path);
}
}
}
}
Because you read the entire stream to extract F1. So next time, for F2, getNextEntry() returned null. You need to either reopen the zip stream, or, better still, pass a list of files to unzip to your method.
I must get file content from ZIP archive (only one file, I know its name) using SFTP. The only thing I'm having is ZIP's InputStream. Most examples show how get content using this statement:
ZipFile zipFile = new ZipFile("location");
But as I said, I don't have ZIP file on my local machine and I don't want to download it. Is an InputStream enough to read?
UPD: This is how I do:
import java.util.zip.ZipInputStream;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
public class SFTP {
public static void main(String[] args) {
String SFTPHOST = "host";
int SFTPPORT = 3232;
String SFTPUSER = "user";
String SFTPPASS = "mypass";
String SFTPWORKINGDIR = "/dir/work";
Session session = null;
Channel channel = null;
ChannelSftp channelSftp = null;
try {
JSch jsch = new JSch();
session = jsch.getSession(SFTPUSER, SFTPHOST, SFTPPORT);
session.setPassword(SFTPPASS);
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
channel = session.openChannel("sftp");
channel.connect();
channelSftp = (ChannelSftp) channel;
channelSftp.cd(SFTPWORKINGDIR);
ZipInputStream stream = new ZipInputStream(channelSftp.get("file.zip"));
ZipEntry entry = zipStream.getNextEntry();
System.out.println(entry.getName); //Yes, I got its name, now I need to get content
} catch (Exception ex) {
ex.printStackTrace();
} finally {
session.disconnect();
channelSftp.disconnect();
channel.disconnect();
}
}
}
Below is a simple example on how to extract a ZIP File, you will need to check if the file is a directory. But this is the simplest.
The step you are missing is reading the input stream and writing the contents to a buffer which is written to an output stream.
// Expands the zip file passed as argument 1, into the
// directory provided in argument 2
public static void main(String args[]) throws Exception
{
if(args.length != 2)
{
System.err.println("zipreader zipfile outputdir");
return;
}
// create a buffer to improve copy performance later.
byte[] buffer = new byte[2048];
// open the zip file stream
InputStream theFile = new FileInputStream(args[0]);
ZipInputStream stream = new ZipInputStream(theFile);
String outdir = args[1];
try
{
// now iterate through each item in the stream. The get next
// entry call will return a ZipEntry for each file in the
// stream
ZipEntry entry;
while((entry = stream.getNextEntry())!=null)
{
String s = String.format("Entry: %s len %d added %TD",
entry.getName(), entry.getSize(),
new Date(entry.getTime()));
System.out.println(s);
// Once we get the entry from the stream, the stream is
// positioned read to read the raw data, and we keep
// reading until read returns 0 or less.
String outpath = outdir + "/" + entry.getName();
FileOutputStream output = null;
try
{
output = new FileOutputStream(outpath);
int len = 0;
while ((len = stream.read(buffer)) > 0)
{
output.write(buffer, 0, len);
}
}
finally
{
// we must always close the output file
if(output!=null) output.close();
}
}
}
finally
{
// we must always close the zip file.
stream.close();
}
}
Code excerpt came from the following site:
http://www.thecoderscorner.com/team-blog/java-and-jvm/12-reading-a-zip-file-from-java-using-zipinputstream#.U4RAxYamixR
Well, I've done this:
zipStream = new ZipInputStream(channelSftp.get("Port_Increment_201405261400_2251.zip"));
zipStream.getNextEntry();
sc = new Scanner(zipStream);
while (sc.hasNextLine()) {
System.out.println(sc.nextLine());
}
It helps me to read ZIP's content without writing to another file.
The ZipInputStream is an InputStream by itself and delivers the contents of each entry after each call to getNextEntry(). Special care must be taken, not to close the stream from which the contents is read, since it is the same as the ZIP stream:
public void readZipStream(InputStream in) throws IOException {
ZipInputStream zipIn = new ZipInputStream(in);
ZipEntry entry;
while ((entry = zipIn.getNextEntry()) != null) {
System.out.println(entry.getName());
readContents(zipIn);
zipIn.closeEntry();
}
}
private void readContents(InputStream contentsIn) throws IOException {
byte contents[] = new byte[4096];
int direct;
while ((direct = contentsIn.read(contents, 0, contents.length)) >= 0) {
System.out.println("Read " + direct + "bytes content.");
}
}
When delegating reading contents to other logic, it can be necessary to wrap the ZipInputStream with a FilterInputStream to close only the entry instead of the whole stream as in:
public void readZipStream(InputStream in) throws IOException {
ZipInputStream zipIn = new ZipInputStream(in);
ZipEntry entry;
while ((entry = zipIn.getNextEntry()) != null) {
System.out.println(entry.getName());
readContents(new FilterInputStream(zipIn) {
#Override
public void close() throws IOException {
zipIn.closeEntry();
}
});
}
}
OP was close. Just need to read the bytes. The call to getNextEntry positions the stream at the beginning of the entry data (docs). If that's the entry we want (or the only entry), then the InputStream is in the right spot. All we need to do is read that entry's decompressed bytes.
byte[] bytes = new byte[(int) entry.getSize()];
int i = 0;
while (i < bytes.length) {
// .read doesn't always fill the buffer we give it.
// Keep calling it until we get all the bytes for this entry.
i += zipStream.read(bytes, i, bytes.length - i);
}
So if these bytes really are text, then we can decode those bytes to a String. I'm just assuming utf8 encoding.
new String(bytes, "utf8")
Side note: I personally use apache commons-io IOUtils to cut down on this kind of lower level stuff. The docs for ZipInputStream.read seem to imply that read will stop at the end of the current zip entry. If that is true, then reading the current textual entry is one line with IOUtils.
String text = IOUtils.toString(zipStream)
Unzip archive (zip) with preserving file structure into given directory.
Note; this code use deps on "org.apache.commons.io.IOUtils"), but you can replace it by yours custom 'read-stream' code
public static void unzipDirectory(File archiveFile, File destinationDir) throws IOException
{
Path destPath = destinationDir.toPath();
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(archiveFile)))
{
ZipEntry zipEntry;
while ((zipEntry = zis.getNextEntry()) != null)
{
Path resolvedPath = destPath.resolve(zipEntry.getName()).normalize();
if (!resolvedPath.startsWith(destPath))
{
throw new IOException("The requested zip-entry '" + zipEntry.getName() + "' does not belong to the requested destination");
}
if (zipEntry.isDirectory())
{
Files.createDirectories(resolvedPath);
} else
{
if(!Files.isDirectory(resolvedPath.getParent()))
{
Files.createDirectories(resolvedPath.getParent());
}
try (FileOutputStream outStream = new FileOutputStream(resolvedPath.toFile()))
{
IOUtils.copy(zis, outStream);
}
}
}
}
}
Here a more generic solution to process a zip inputstream with a BiConsumer. It's nearly the same solution that was used by haui
private void readZip(InputStream is, BiConsumer<ZipEntry,InputStream> consumer) throws IOException {
try (ZipInputStream zipFile = new ZipInputStream(is);) {
ZipEntry entry;
while((entry = zipFile.getNextEntry()) != null){
consumer.accept(entry, new FilterInputStream(zipFile) {
#Override
public void close() throws IOException {
zipFile.closeEntry();
}
});
}
}
}
You can use it by just calling
readZip(<some inputstream>, (entry, is) -> {
/* don't forget to close this stream after processing. */
is.read() // ... <- to read each entry
});
If content of your ZIP consist of 1 file (for example, zipped content of HTTP response), you can read text content using Kotlin as follows:
#Throws(IOException::class)
fun InputStream.readZippedContent() = ZipInputStream(this).use { stream ->
stream.nextEntry?.let { stream.bufferedReader().readText() } ?: String()
}
This extension function unzips first ZIP entry of Zip file and read content as plain text.
Usage:
val inputStream: InputStream = ... // your zipped InputStream
val textContent = inputStream.readZippedContent()
Here i have folder(ZipFilesFolder) in that it consist of 10 zip files say one.zip,two.zip,three.zip..ten.zip,i'm passing file every time from this folder to zipFileToUnzip as zipFilename.I need the result in the same folder(ZipFilesFolder)i need to unzip those files and instead of one.zip,two.zip,..one,two,three folder has to visible.
public static void zipFileToUnzip(File zipFilename) throws IOException {
try {
//String destinationname = "D:\\XYZ";
byte[] buf = new byte[1024];
ZipInputStream zipinputstream = null;
ZipEntry zipentry;
zipinputstream = new ZipInputStream(new FileInputStream(zipFilename));
zipentry = zipinputstream.getNextEntry();
while (zipentry != null) {
//for each entry to be extracted
String entryName = zipentry.getName();
System.out.println("entryname " + entryName);
int n;
FileOutputStream fileoutputstream;
File newFile = new File(entryName);
String directory = newFile.getParent();
if (directory == null) {
if (newFile.isDirectory()) {
break;
}
}
fileoutputstream = new FileOutputStream(
destinationname + entryName);
while ((n = zipinputstream.read(buf, 0, 1024)) > -1) {
fileoutputstream.write(buf, 0, n);
}
fileoutputstream.close();
zipinputstream.closeEntry();
zipentry = zipinputstream.getNextEntry();
}//while
zipinputstream.close();
} catch (IOException e) {
}
}
This is my code ,but it is not working,could anybody help me,how to get desired output.
There are a couple of problems with your code:
it does not compile since destinationname is commented, but referenced when opening the FileOutputStream
IOExceptions are caught and ignored. If you throw them you would get error messages that could help you diagnose the problem
when opening the FileOutputStream, you just concatenate two strings without adding a path-separator in between.
if the file to be created is in a directory, the directory is not created and thus FileOutputStream cannot create the file.
streams are not closed when exceptions occur.
If you do not mind using guava, which simplifies life when it comes to copying streams to files, you could use this code instead:
public static void unzipFile(File zipFile) throws IOException {
File destDir = new File(zipFile.getParentFile(), Files.getNameWithoutExtension(zipFile.getName()));
try(ZipInputStream zipStream = new ZipInputStream(new FileInputStream(zipFile))) {
ZipEntry zipEntry = zipStream.getNextEntry();
if(zipEntry == null) throw new IOException("Empty or no zip-file");
while(zipEntry != null) {
File destination = new File(destDir, zipEntry.getName());
if(zipEntry.isDirectory()) {
destination.mkdirs();
} else {
destination.getParentFile().mkdirs();
Files.asByteSink(destination).writeFrom(zipStream);
}
zipEntry = zipStream.getNextEntry();
}
}
}
Alternatively you might also use zip4j, see also this question.
I'm using Apache Commons Compress to create tar archives and decompress them. My problems start with this method:
private void decompressFile(File file) throws IOException {
logger.info("Decompressing " + file.getName());
BufferedOutputStream outputStream = null;
TarArchiveInputStream tarInputStream = null;
try {
tarInputStream = new TarArchiveInputStream(
new FileInputStream(file));
TarArchiveEntry entry;
while ((entry = tarInputStream.getNextTarEntry()) != null) {
if (!entry.isDirectory()) {
File compressedFile = entry.getFile();
File tempFile = File.createTempFile(
compressedFile.getName(), "");
byte[] buffer = new byte[BUFFER_MAX_SIZE];
outputStream = new BufferedOutputStream(
new FileOutputStream(tempFile), BUFFER_MAX_SIZE);
int count = 0;
while ((count = tarInputStream.read(buffer, 0, BUFFER_MAX_SIZE)) != -1) {
outputStream.write(buffer, 0, count);
}
}
deleteFile(file);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
outputStream.flush();
outputStream.close();
}
}
}
Every time I run the code, compressedFile variable is null, but the while loop is iterating over all entries in my test tar.
Could you help me to understand what I'm doing wrong?
From the official documentation
Reading entries from an tar archive:
TarArchiveEntry entry = tarInput.getNextTarEntry();
byte[] content = new byte[entry.getSize()];
LOOP UNTIL entry.getSize() HAS BEEN READ {
tarInput.read(content, offset, content.length - offset);
}
I have written an example starting from your implementation and testing with a very trivial .tar (just one entry of text).
Not knowing the exact requirement I just take care of solving the problem of reading the archive avoiding the nullpointer. Debugging, the entry is available as you also have found
private static void decompressFile(File file) throws IOException {
BufferedOutputStream outputStream = null;
TarArchiveInputStream tarInputStream = null;
try {
tarInputStream = new TarArchiveInputStream(
new FileInputStream(file));
TarArchiveEntry entry;
while ((entry = tarInputStream.getNextTarEntry()) != null) {
if (!entry.isDirectory()) {
File compressedFile = entry.getFile();
String name = entry.getName();
int size = 0;
int c;
while (size < entry.getSize()) {
c = tarInputStream.read();
System.out.print((char) c);
size++;
}
(.......)
AS I said: I tested with a tar including only an entry of text (you can also try this approach to verify the code) to be sure that the null is avoided.
You need to make all the needed adaptations for your real needs.
It is clear that you will have to handle streams as in the metacode I posted on top.
It shows how to deal with the single entries.
Try using getNextEntry() method instead of getNextTarEntry() method.
The second method returns a TarArchiveEntry. Probably this is not what you want!
I am having a zip file into my project. When I am running my code through IDE, my extract(String file, String destination) method works fine.
D:/Tools/JAVA/Lodable_Creation/build/classes/ib2.zip-->
String s1=getClass().getResource("Test.zip").getPath().toString();
extract(s1, "c:\\");
This is giving me Path s1 is--> D:\Tools\JAVA\Lodable_Creation\build
When I compile same code and run through Command prompt
file:/D:/Tools/JAVA/Lodable_Creation/dist/Lodable_Creation.jar!/Test.zip
s1 is-->D:\Tools\JAVA\Lodable_Creation\dist
And I am not getting output. Please help me.
UPDATE:-
public static void extract(String file, String destination) throws IOException {
ZipInputStream in = null;
OutputStream out = null;
try {
// Open the ZIP file
in = new ZipInputStream(new FileInputStream(file));
// Get the first entry
ZipEntry entry = null;
while ((entry = in.getNextEntry()) != null) {
String outFilename = entry.getName();
// Open the output file
if (entry.isDirectory()) {
new File(destination, outFilename).mkdirs();
} else {
out = new FileOutputStream(new File(destination,outFilename));
// Transfer bytes from the ZIP file to the output file
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.close();
}
}
} finally {
// Close the stream
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
}
On Ok Click button
Map map = System.getenv();
Set keys = map.keySet();
String newString = (String) map.get("CYGWIN_HOME");
System.out.println(" " + newString);
String destination= newString.replace(";", "");
System.out.println(" " + destination);
String S =getClass().getResource("Test.zip").getPath().toString();
File jarFile = new File(S);
String file=jarFile.toString();
extract(file,destination);
This is my actual code for extract method and on OK Button. This is extracting Test.zip file to the Destination folder. i.e CYGWIN_HOME
If your file path is actually a URL (starts with "file://") then use new ZipInputStream((new URL(file)).openStream()) otherwise use new ZipInputStream(new FileInputStream(file)) like you already do.