This question already has answers here:
How to create Uncompressed Zip archive in Java
(4 answers)
Closed 6 years ago.
I'm trying to compress directory content into zip archive using java.
Everything is fine, but I just want to clarify some facts.
Here is the code which I use to compress files:
public void pack(#Nonnull String archiveName, #Nonnull File outputDir, #Nonnull File targetDir) {
File zipFile = new File(outputDir, "out.zip");
ZipOutputStream zipOutputStream = null;
OutputStream outputStream;
try {
// create stream for writing zip archive
outputStream = new FileOutputStream(zipFile);
zipOutputStream = new ZipOutputStream(outputStream);
// write files recursively
writeFiles(zipOutputStream, targetDir.listFiles(), "");
} catch (IOException e) {
LOGGER.error("IO exception while packing files to archive", e);
} finally {
// close output streams
if (zipOutputStream != null) {
try {
zipOutputStream.close();
} catch (IOException e) {
LOGGER.error("Unable to close zip output stream", e);
}
}
}
}
/**
* Writes specified files and their children (in case of directories) to archive
*
* #param zipOutputStream archive output stream
* #param files which should be added to archive
* #param path path relative of root of archive where files should be placed
*/
private void writeFiles(#Nonnull ZipOutputStream zipOutputStream, #Nullable File[] files, #Nonnull String path) throws IOException {
if (files == null || files.length == 0) {
return;
}
for (File file : files) {
if (file.isDirectory()) {
// recursively add files in this directory
String fullDirectoryName = path + file.getName() + "/";
File[] childFiles = file.listFiles();
if (childFiles != null && childFiles.length > 0) {
// write child files to archive. current directory will be created automatically
writeFiles(zipOutputStream, childFiles, fullDirectoryName);
} else {
// empty directory. write directory itself to archive
ZipEntry entry = new ZipEntry(fullDirectoryName);
zipOutputStream.putNextEntry(entry);
zipOutputStream.closeEntry();
}
} else {
// put file in archive
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(file));
zipOutputStream.putNextEntry(new ZipEntry(path + file.getName()));
ByteStreams.copy(bufferedInputStream, zipOutputStream);
zipOutputStream.closeEntry();
bufferedInputStream.close();
}
}
}
Now there are the questions:
Is it correct that by default (and in my case too) I will get already compressed archive (using Deflate method)?
How to get uncompressed archive:
If I set method zipOutputStream.setMethod(ZipOutputStream.STORED) I have to provide size, compressed size (is it will be equal to size?) and crc, otherwise I will get exceptions
If I don't want to calculate size and crc by myself I can use DEFLATE method with zero level:
zipOutputStream.setMethod(ZipOutputStream.DEFLATED);
zipOutputStream.setLevel(ZipOutputStream.STORED);So, is it correct that in this case I get not compressed archive at all?
Is there more convenient-obvious method to creating not-compressed archives?
Rather than re-invent the wheel I'd seriously consider using an existing library for this, such as Apache Ant. The basic idiom for creating a zip file is:
Project p = new Project();
p.init();
Zip zip = new Zip();
zip.setProject(p);
zip.setDestFile(new File(outputDir, "out.zip"));
FileSet fs = new FileSet();
fs.setProject(p);
fs.setDirectory(targetDir);
zip.addFileset(fs);
zip.perform();
By default you will get a compressed archive. For an uncompressed zip all you need to add is
zip.setCompress(false);
after the setDestFile (in fact anywhere before the perform).
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);
}
I have gone through the link of how to extract a .tar file and several link on SOF using Java.
However, I didnt find any which can relate to my concerns which is multilevel or nested .tar/.tgz/.zip file.
my concern is with something like below
Abc.tar.gz
--DEF.tar
--sample1.txt
--sample2.txt
--FGH.tgz
--sample3.txt
-sample4.txt
This is the simple one which I can give here . As it can be in any compressed combination with the folder like .tar inside .tar and .gz and again .tgz and so on....
My problem is I am able to extract till the first level using Apache Commons Compress library. that is if Abc.tar.gz gets extracted then in the destination/output folder its only DEF.tar available . beyond that my extraction is not working.
I tried to give the output of first to the input to the second on the fly but I got stuck with FileNotFoundException. As at that point of time output file would have not been in place and the second extraction not able to get the file.
Pseudocode:
public class CommonExtraction {
TarArchiveInputStream tar = null;
if((sourcePath.trim().toLowerCase.endsWith(".tar.gz")) || sourcePath.trim().toLowerCase.endsWith(".tgz")) {
try {
tar=new TarArchiveInputStream(new GzipCompressorInputStream(new BufferedInputStream(new FileInputStream(sourcePath))));
extractTar(tar,destPath)
} catch (Exception e) {
e.printStackTrace();
}
}
}
Public static void extractTar(TarArchiveInputStream tar, String outputFolder) {
try{
TarArchiveEntry entry;
while (null!=(entry=(TarArchiveEntry)tar.getNextTarEntry())) {
if(entry.getName().trim().toLowerCase.endsWith(".tar")){
final String path = outputFolder + entry.getName()
tar=new TarArchiveInputStream(new BufferedInputStream(new FileInputStream(path))) // failing as .tar folder after decompression from .gz not available at destination path
extractTar(tar,outputFolder)
}
extractEntry(entry,tar,outputFolder)
}
tar.close();
}catch(Exception ex){
ex.printStackTrace();
}
}
Public static void extractEntry(TarArchiveEntry entry , InputStream tar, String outputFolder){
final String path = outputFolder + entry.getName()
if(entry.isDirectory()){
new File(path).mkdirs();
}else{
//create directory for the file if not exist
}
// code to read and write until last byte is encountered
}
}
Ps: please ignore the syntax and all in the code.
Try this
try (InputStream fi = file.getInputStream();
InputStream bi = new BufferedInputStream(fi);
InputStream gzi = new GzipCompressorInputStream(bi, false);
ArchiveInputStream archive = new TarArchiveInputStream(gzi)) {
withArchiveStream(archive, result::appendEntry);
}
As i see what .tar.gz and .tgz is same formats. And my method withArchiveEntry is:
private void withArchiveStream(ArchiveInputStream archInStream, BiConsumer<ArchiveInputStream, ArchiveEntry> entryConsumer) throws IOException {
ArchiveEntry entry;
while((entry = archInStream.getNextEntry()) != null) {
entryConsumer.accept(archInStream, entry);
}
}
private void appendEntry(ArchiveInputStream archive, ArchiveEntry entry) {
if (!archive.canReadEntryData(entry)) {
throw new IOException("Can`t read archive entry");
}
if (entry.isDirectory()) {
return;
}
// And for example
String content = new String(archive.readAllBytes(), StandardCharsets.UTF_8);
System.out.println(content);
}
You have a recursive problem, so you can use recursion to solve it. Here is some pseudocode to show how it can be done:
public class ArchiveExtractor
{
public void extract(File file)
{
List<File> files; // list of extracted files
if(isZip(file))
files = extractZip(file);
else if(isTGZ(file))
files = extractTGZ(file);
else if(isTar(file))
files = extractTar(file);
else if(isGZip(file))
files = extractGZip(file);
for(File f : files)
{
if(isArchive(f))
extract(f); // recursive call
}
}
private List<File> extractZip(File file)
{
// extract archive and return list of extracted files
}
private List<File> extractTGZ(File file)
{
// extract archive and return list of extracted files
}
private List<File> extractTar(File file)
{
// extract archive and return list of extracted files
}
private List<File> extractGZip(File file)
{
// extract archive and return list of extracted file
}
}
where:
isZip() tests if the file extension is zip
isTGZ() tests if the file extension is tgz
isTar() tests if the file extension is tar
isGZip() tests if the file extension is gz
isArchive() means isZip() || isTGZ() || isTar() || isGZip()
As for the directory where each archive is extracted: you are free to do as you want.
If you process test.zip for example, you may extract in the same directory as where the archive is,
or create the directory test and extract in it.
How do you move a file from one location to another? When I run my program any file created in that location automatically moves to the specified location. How do I know which file is moved?
myFile.renameTo(new File("/the/new/place/newName.file"));
File#renameTo does that (it can not only rename, but also move between directories, at least on the same file system).
Renames the file denoted by this abstract pathname.
Many aspects of the behavior of this method are inherently platform-dependent: The rename operation might not be able to move a file from one filesystem to another, it might not be atomic, and it might not succeed if a file with the destination abstract pathname already exists. The return value should always be checked to make sure that the rename operation was successful.
If you need a more comprehensive solution (such as wanting to move the file between disks), look at Apache Commons FileUtils#moveFile
With Java 7 or newer you can use Files.move(from, to, CopyOption... options).
E.g.
Files.move(Paths.get("/foo.txt"), Paths.get("bar.txt"), StandardCopyOption.REPLACE_EXISTING);
See the Files documentation for more details
Java 6
public boolean moveFile(String sourcePath, String targetPath) {
File fileToMove = new File(sourcePath);
return fileToMove.renameTo(new File(targetPath));
}
Java 7 (Using NIO)
public boolean moveFile(String sourcePath, String targetPath) {
boolean fileMoved = true;
try {
Files.move(Paths.get(sourcePath), Paths.get(targetPath), StandardCopyOption.REPLACE_EXISTING);
} catch (Exception e) {
fileMoved = false;
e.printStackTrace();
}
return fileMoved;
}
File.renameTo from Java IO can be used to move a file in Java. Also see this SO question.
To move a file you could also use Jakarta Commons IOs FileUtils.moveFile
On error it throws an IOException, so when no exception is thrown you know that that the file was moved.
Just add the source and destination folder paths.
It will move all the files and folder from source folder to
destination folder.
File destinationFolder = new File("");
File sourceFolder = new File("");
if (!destinationFolder.exists())
{
destinationFolder.mkdirs();
}
// Check weather source exists and it is folder.
if (sourceFolder.exists() && sourceFolder.isDirectory())
{
// Get list of the files and iterate over them
File[] listOfFiles = sourceFolder.listFiles();
if (listOfFiles != null)
{
for (File child : listOfFiles )
{
// Move files to destination folder
child.renameTo(new File(destinationFolder + "\\" + child.getName()));
}
// Add if you want to delete the source folder
sourceFolder.delete();
}
}
else
{
System.out.println(sourceFolder + " Folder does not exists");
}
Files.move(source, target, REPLACE_EXISTING);
You can use the Files object
Read more about Files
You could execute an external tool for that task (like copy in windows environments) but, to keep the code portable, the general approach is to:
read the source file into memory
write the content to a file at the new location
delete the source file
File#renameTo will work as long as source and target location are on the same volume. Personally I'd avoid using it to move files to different folders.
Try this :-
boolean success = file.renameTo(new File(Destdir, file.getName()));
Wrote this method to do this very thing on my own project only with the replace file if existing logic in it.
// we use the older file i/o operations for this rather than the newer jdk7+ Files.move() operation
private boolean moveFileToDirectory(File sourceFile, String targetPath) {
File tDir = new File(targetPath);
if (tDir.exists()) {
String newFilePath = targetPath+File.separator+sourceFile.getName();
File movedFile = new File(newFilePath);
if (movedFile.exists())
movedFile.delete();
return sourceFile.renameTo(new File(newFilePath));
} else {
LOG.warn("unable to move file "+sourceFile.getName()+" to directory "+targetPath+" -> target directory does not exist");
return false;
}
}
Please try this.
private boolean filemovetoanotherfolder(String sourcefolder, String destinationfolder, String filename) {
boolean ismove = false;
InputStream inStream = null;
OutputStream outStream = null;
try {
File afile = new File(sourcefolder + filename);
File bfile = new File(destinationfolder + filename);
inStream = new FileInputStream(afile);
outStream = new FileOutputStream(bfile);
byte[] buffer = new byte[1024 * 4];
int length;
// copy the file content in bytes
while ((length = inStream.read(buffer)) > 0) {
outStream.write(buffer, 0, length);
}
// delete the original file
afile.delete();
ismove = true;
System.out.println("File is copied successful!");
} catch (IOException e) {
e.printStackTrace();
}finally{
inStream.close();
outStream.close();
}
return ismove;
}
I am trying to put files from a folder inside a zip file in the following structure:
Folder structure:
myFolder
|-file1.txt
|-file2.txt
|-folder172
|-file817.txt
|-file818.txt
...
Supposed structure inside ZipFile:
file1.txt
file2.txt
folder172
|-file817.txt
|-file818.txt
This is my code:
public static void writeZip(String path) throws IOException{
FileOutputStream fos = new FileOutputStream(path+File.separator+"atest.zip");
ZipOutputStream zos = new ZipOutputStream(fos);
try {
Files.walk(Paths.get(path)).filter(Files::isRegularFile).forEach((string) -> addToZipFile(string.toString(),zos));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
zos.close();
fos.close();
}
public static void addToZipFile(String fileName, ZipOutputStream zos) throws IOException {
System.out.println("Writing '" + fileName + "' to zip file");
File file = new File(fileName);
FileInputStream fis = null;
fis = new FileInputStream(file);
ZipEntry zipEntry = new ZipEntry(fileName);
zos.putNextEntry(zipEntry);
byte[] bytes = new byte[1024];
int length;
while ((length = fis.read(bytes)) >= 0) {
zos.write(bytes, 0, length);
}
zos.closeEntry();
fis.close();
}
The problem is now, when i call writeZip("/home/arthur/.grutil/");, i get the following structure in the zip-file:
home
|-arthur
|-.grutil
|-file1.txt
|-file2.txt
|-folder172
|-file817.txt
|-file818.txt
...
How do i need to change my code to get the supposed structure (as described above) and not the structure with the full path '/home/arthur/.grutil/ ...'?
Whilst this can be done with the ancient ZipOutputStream I would recommend against it.
It is much more intuitive to think about a Zip archive as a compressed filesystem inside a file, than a stream of bytes. For this reason, Java provides the ZipFileSystem.
So all you need to do is open the Zip as a FileSystem and then manually copy files across.
There are a couple of gotchas:
You need to only copy files, directories need to be created.
The NIO API does not support operations such as relativize across different filesystems (reasons should be obvious) so this you need to do yourself.
Here are a couple of simple methods that will do exactly that:
/**
* This creates a Zip file at the location specified by zip
* containing the full directory tree rooted at contents
*
* #param zip the zip file, this must not exist
* #param contents the root of the directory tree to copy
* #throws IOException, specific exceptions thrown for specific errors
*/
public static void createZip(final Path zip, final Path contents) throws IOException {
if (Files.exists(zip)) {
throw new FileAlreadyExistsException(zip.toString());
}
if (!Files.exists(contents)) {
throw new FileNotFoundException("The location to zip must exist");
}
final Map<String, String> env = new HashMap<>();
//creates a new Zip file rather than attempting to read an existing one
env.put("create", "true");
// locate file system by using the syntax
// defined in java.net.JarURLConnection
final URI uri = URI.create("jar:file:/" + zip.toString().replace("\\", "/"));
try (final FileSystem zipFileSystem = FileSystems.newFileSystem(uri, env);
final Stream<Path> files = Files.walk(contents)) {
final Path root = zipFileSystem.getPath("/");
files.forEach(file -> {
try {
copyToZip(root, contents, file);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
/**
* Copy a specific file/folder to the zip archive
* If the file is a folder, create the folder. Otherwise copy the file
*
* #param root the root of the zip archive
* #param contents the root of the directory tree being copied, for relativization
* #param file the specific file/folder to copy
*/
private static void copyToZip(final Path root, final Path contents, final Path file) throws IOException {
final Path to = root.resolve(contents.relativize(file).toString());
if (Files.isDirectory(file)) {
Files.createDirectories(to);
} else {
Files.copy(file, to);
}
}
I want to copy a file from a jar. The file that I am copying is going to be copied outside the working directory. I have done some tests and all methods I try end up with 0 byte files.
EDIT: I want the copying of the file to be done via a program, not manually.
First of all I want to say that some answers posted before are entirely correct, but I want to give mine, since sometimes we can't use open source libraries under the GPL, or because we are too lazy to download the jar XD or what ever your reason is here is a standalone solution.
The function below copy the resource beside the Jar file:
/**
* Export a resource embedded into a Jar file to the local file path.
*
* #param resourceName ie.: "/SmartLibrary.dll"
* #return The path to the exported resource
* #throws Exception
*/
static public String ExportResource(String resourceName) throws Exception {
InputStream stream = null;
OutputStream resStreamOut = null;
String jarFolder;
try {
stream = ExecutingClass.class.getResourceAsStream(resourceName);//note that each / is a directory down in the "jar tree" been the jar the root of the tree
if(stream == null) {
throw new Exception("Cannot get resource \"" + resourceName + "\" from Jar file.");
}
int readBytes;
byte[] buffer = new byte[4096];
jarFolder = new File(ExecutingClass.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()).getParentFile().getPath().replace('\\', '/');
resStreamOut = new FileOutputStream(jarFolder + resourceName);
while ((readBytes = stream.read(buffer)) > 0) {
resStreamOut.write(buffer, 0, readBytes);
}
} catch (Exception ex) {
throw ex;
} finally {
stream.close();
resStreamOut.close();
}
return jarFolder + resourceName;
}
Just change ExecutingClass to the name of your class, and call it like this:
String fullPath = ExportResource("/myresource.ext");
Edit for Java 7+ (for your convenience)
As answered by GOXR3PLUS and noted by Andy Thomas you can achieve this with:
Files.copy( InputStream in, Path target, CopyOption... options)
See GOXR3PLUS answer for more details
Given your comment about 0-byte files, I have to assume you're trying to do this programmatically, and, given your tags, that you're doing it in Java. If that's true, then just use Class.getResource() to get a URL pointing to the file in your JAR, then Apache Commons IO FileUtils.copyURLToFile() to copy it out to the file system. E.g.:
URL inputUrl = getClass().getResource("/absolute/path/of/source/in/jar/file");
File dest = new File("/path/to/destination/file");
FileUtils.copyURLToFile(inputUrl, dest);
Most likely, the problem with whatever code you have now is that you're (correctly) using a buffered output stream to write to the file but (incorrectly) failing to close it.
Oh, and you should edit your question to clarify exactly how you want to do this (programmatically, not, language, ...)
Faster way to do it with Java 7+ , plus code to get the current directory:
/**
* Copy a file from source to destination.
*
* #param source
* the source
* #param destination
* the destination
* #return True if succeeded , False if not
*/
public static boolean copy(InputStream source , String destination) {
boolean succeess = true;
System.out.println("Copying ->" + source + "\n\tto ->" + destination);
try {
Files.copy(source, Paths.get(destination), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException ex) {
logger.log(Level.WARNING, "", ex);
succeess = false;
}
return succeess;
}
Testing it (icon.png is an image inside the package image of the application):
copy(getClass().getResourceAsStream("/image/icon.png"),getBasePathForClass(Main.class)+"icon.png");
About the line of code (getBasePathForClass(Main.class)): -> check the answer i have added here :) -> Getting the Current Working Directory in Java
Java 8 (actually FileSystem is there since 1.7) comes with some cool new classes/methods to deal with this. As somebody already mentioned that JAR is basically ZIP file, you could use
final URI jarFileUril = URI.create("jar:file:" + file.toURI().getPath());
final FileSystem fs = FileSystems.newFileSystem(jarFileUri, env);
(See Zip File)
Then you can use one of the convenient methods like:
fs.getPath("filename");
Then you can use Files class
try (final Stream<Path> sources = Files.walk(from)) {
sources.forEach(src -> {
final Path dest = to.resolve(from.relativize(src).toString());
try {
if (Files.isDirectory(from)) {
if (Files.notExists(to)) {
log.trace("Creating directory {}", to);
Files.createDirectories(to);
}
} else {
log.trace("Extracting file {} to {}", from, to);
Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING);
}
} catch (IOException e) {
throw new RuntimeException("Failed to unzip file.", e);
}
});
}
Note: I tried that to unpack JAR files for testing
Robust solution:
public static void copyResource(String res, String dest, Class c) throws IOException {
InputStream src = c.getResourceAsStream(res);
Files.copy(src, Paths.get(dest), StandardCopyOption.REPLACE_EXISTING);
}
You can use it like this:
File tempFileGdalZip = File.createTempFile("temp_gdal", ".zip");
copyResource("/gdal.zip", tempFileGdalZip.getAbsolutePath(), this.getClass());
Use the JarInputStream class:
// assuming you already have an InputStream to the jar file..
JarInputStream jis = new JarInputStream( is );
// get the first entry
JarEntry entry = jis.getNextEntry();
// we will loop through all the entries in the jar file
while ( entry != null ) {
// test the entry.getName() against whatever you are looking for, etc
if ( matches ) {
// read from the JarInputStream until the read method returns -1
// ...
// do what ever you want with the read output
// ...
// if you only care about one file, break here
}
// get the next entry
entry = jis.getNextEntry();
}
jis.close();
See also: JarEntry
To copy a file from your jar, to the outside, you need to use the following approach:
Get a InputStream to a the file inside your jar file using getResourceAsStream()
We open our target file using a FileOutputStream
We copy bytes from the input to the output stream
We close our streams to prevent resource leaks
Example code that also contains a variable to not replace the existing values:
public File saveResource(String name) throws IOException {
return saveResource(name, true);
}
public File saveResource(String name, boolean replace) throws IOException {
return saveResource(new File("."), name, replace)
}
public File saveResource(File outputDirectory, String name) throws IOException {
return saveResource(outputDirectory, name, true);
}
public File saveResource(File outputDirectory, String name, boolean replace)
throws IOException {
File out = new File(outputDirectory, name);
if (!replace && out.exists())
return out;
// Step 1:
InputStream resource = this.getClass().getResourceAsStream(name);
if (resource == null)
throw new FileNotFoundException(name + " (resource not found)");
// Step 2 and automatic step 4
try(InputStream in = resource;
OutputStream writer = new BufferedOutputStream(
new FileOutputStream(out))) {
// Step 3
byte[] buffer = new byte[1024 * 4];
int length;
while((length = in.read(buffer)) >= 0) {
writer.write(buffer, 0, length);
}
}
return out;
}
A jar is just a zip file. Unzip it (using whatever method you're comfortable with) and copy the file normally.
${JAVA_HOME}/bin/jar -cvf /path/to.jar