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);
}
}
Related
I have file in the following structure:
--BA.zip
|
|--- BA (directory)
|
|---BA_KKSSI_20201013.zip
| |
| |---BA_KKSSI_20201013.txt
|---BA_KKSSI_20201014.zip
| |
| |---BA_KKSSI_20201014.txt
|---BA_KKSSI_20201015.zip
|
|---BA_KKSSI_20201015.txt
I need to read BA_KKSSI_20201013.txt without extracting the parent file which is BA.zip
I have already written parts of code to read if there is no sub dirs. For example:
public static String readChildZip(Path zipPath) throws IOException {
try (ZipFile zipFile = new ZipFile(zipPath.toFile())) {
// since there is only one text file
ZipEntry textFile = zipFile.entries().nextElement();
// the zip
System.out.println(zipFile.getName());
InputStream is = zipFile.getInputStream(textFile);
String contents = IOUtils.toString(is, StandardCharsets.UTF_8);
return contents;
}
}
Above code can process the last zip and txt part (i.e., if there are no sub-dirs within a zip)
I looked through most of the SO posts and all of them propose extracting the sub-directory first and then read through the secondary zip files.
Is there a way to do this without extracting in the first place?
You can use ZipInputStream (https://docs.oracle.com/javase/7/docs/api/java/util/zip/ZipInputStream.html) to read entries in the "outer" zip file as zip files as well.
Meaning open the zip file as you have but then iterate over and if a entry is a zipfile itself you create a ZipInputStream with the InputStream for that ZipEntry.
This returns the contents of the first text file inside the first Zip file in zipPath.
public static String readChildZip(Path zipPath) throws IOException {
try (ZipFile zipFile = new ZipFile(zipPath.toFile())) {
ZipEntry childZipEntry = zipFile.entries().nextElement();
try (InputStream childInputStream = zipFile.getInputStream(childZipEntry);
ZipInputStream childZipStream = new ZipInputStream(childInputStream)) {
childZipStream.getNextEntry();
return new String(childZipStream.readAllBytes(), StandardCharsets.UTF_8);
}
}
}
And this will print the contents of all text files inside the first Zip file in zipPath.
public static void readChildZipAll(Path zipPath) throws IOException {
try (ZipFile zipFile = new ZipFile(zipPath.toFile())) {
ZipEntry childZipEntry = zipFile.entries().nextElement();
try (InputStream childInputStream = zipFile.getInputStream(childZipEntry);
ZipInputStream childZipStream = new ZipInputStream(childInputStream)) {
ZipEntry grandChildEntry;
while ((grandChildEntry = childZipStream.getNextEntry()) != null) {
System.out.println(grandChildEntry + ":"
+ new String(childZipStream.readAllBytes(), StandardCharsets.UTF_8));
}
}
}
}
You should look at using NIO ZIP File System or ZipInputStream if wanting to scan a ZIP.
Here is an example of using ZIP File System in a recursive scanner which can be used to inspect any level of depth of JAR/ZIP/WAR/EAR hierarchy. You should adapt to suit own purposes for whatever action you need to perform on the content, this example just cats any ".txt" files to the console.
Note that ZIP File System returns zip filesystem Path objects which can be used with NIO Files.xxx() calls such as Files.find() and Find.copy() just like you would use for Path that originate from default HDD filesystems.
private static Pattern ZIP_PATTERN = Pattern.compile("(?i).*\\.(jar|war|ear|zip)");
public static void traverseZip(Path zip) {
System.out.println("traverseZip "+zip.toAbsolutePath());
try (FileSystem fs = FileSystems.newFileSystem(zip)) {
for (Path root : fs.getRootDirectories()) {
try (Stream<Path> stream = Files.find(root, Integer.MAX_VALUE, (p,a) -> true)) {
stream.forEach(entry -> {
System.out.println(zip.toString()+" -> "+entry);
// SOME ACTION HERE, for example
if (entry.toString().endsWith(".txt")) {
cat(entry, System.out);
}
if (ZIP_PATTERN.matcher(entry.toString()).matches() && Files.isRegularFile(entry)) {
traverseZip(entry);
}
});
}
}
}
catch(IOException e) {
throw new UncheckedIOException(e);
}
}
private static void cat(Path path, OutputStream out) {
try {
Files.copy(path, out);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
Launch with:
traverseZip(Path.of("some.zip"));
I want to manipulate a jar using the standard nio Files and Paths methods. So, Java has a way to do this by creating a zip FileSystem:
try {
zipFS = FileSystems.newFileSystem(zipDisk, zipFSproperties);
} catch(Exception e) {
System.out.println(e.getMessage());
}
My test program uses an existing jar file as a FileSystem and it lists the entries contained in the jar. All that works great. I then copy a new file into the jar and list the entries again. And just as you would expect, the list now contains the newly added file. The problem is after the program closes, I open up the jar file that the jar filesystem is based upon and it doesn't have the new entry added to it. So that's my question! Shouldn't the jar file itself be changed when I add a new entry. I don't know of any commands I can issue the would cause the zip FileSystem to update to the actual jar file that the zip FileSystem wraps. Am I reading more into a FileSystem; are changes in the zip filesystem suppose to cause the corresponding backend zip file to be updated.
code:
public class Main {
public static void main(String[] args) throws IOException {
ZipFileSystem zipFS = new ZipFileSystem("C:\\Temp\\mylibrary\\build\\outputs\\jar\\temp\\mylibrary-debug.zip");
Stream<Path> paths = Files.find(zipFS.zipFS.getRootDirectories().iterator().next().getRoot(),10, (path, basicFileAttributes) -> {
return !Files.isDirectory(path);
});
paths.forEach( path ->
System.out.println ("zip contains entry: " + path)
);
File file = new File("C:\\Temp\\mylibrary\\src\\main\\java\\com\\phinneyridge\\android\\myLib.java");
System.out.println("copying " + file.getPath());
Path outPath = zipFS.zipFS.getPath("myLib.java");
Files.copy (file.toPath(), outPath, StandardCopyOption.REPLACE_EXISTING);
paths = Files.find(zipFS.zipFS.getPath(""),10, (path, basicFileAttributes) -> {
return !Files.isDirectory(path);
});
paths.forEach( path ->
System.out.println ("zip contains entry: " + path)
);
}
}
I added code that shows me accessing a zip file, listing the current entries it contains, adding a new entry (via file copy), and lastly listing the contents again. All of this code works correctly. What doesn't work is that the changes to the zip filesystem don't get incorporated back into the zip file when the application ends. I was surprised that the zip file didn't get updated, but I'm now under the opinion, that it's working as it is intended to work; not doing what I wanted it to do, but that's okay. I can't find any documentation that says it would update the jar file that the FileSystem object originated from. So I'm basically asking is that the correct behavior, or is there something I'm entirely missing to cause the zip FileSystem object to update the Zip file?
Here's the code when I tried Dunc suggestion:
public static void main(String[] args) throws IOException {
ZipFileSystem zipFS = new ZipFileSystem("C:\\Temp\\mylibrary\\build\\outputs\\jar\\temp\\mylibrary-debug.zip");
try (FileSystem fs = zipFS.zipFS) {
try (Stream<Path> paths = Files.find(zipFS.zipFS.getRootDirectories().
iterator().next().getRoot(), 10, (path, basicFileAttributes) -> {
return !Files.isDirectory(path);
})) {
paths.forEach(path ->
System.out.println("zip contains entry: " + path)
);
}
File file = new File("C:\\Temp\\mylibrary\\src\\main\\java\\com\\phinneyridge\\android\\myLib.java");
System.out.println("copying " + file.getPath());
Path outPath = fs.getPath("myLib.java");
Files.copy(file.toPath(), outPath, StandardCopyOption.REPLACE_EXISTING);
try (Stream<Path> paths = Files.find(zipFS.zipFS.getRootDirectories().
iterator().next().getRoot(), 10, (path, basicFileAttributes) -> {
return !Files.isDirectory(path);
})) {
paths.forEach(path ->
System.out.println("zip contains entry: " + path)
);
}
} catch (Exception e) {
System.out.println("FileSystem Error: " + e.getClass().getName() + " - " + e.getMessage());
e.printStackTrace();
}
}
}
And by the way ZipFileSystem is a wrapper class around the FileSystem. I'll post that code too, incase that's where I 'm doing something wrong.
public class ZipFileSystem {
FileSystem zipFS;
Path zipFSPath;
/**
* Constructor for a ZipFile object
* #param zipFilePath string representing the path to the zipfile. If the path doesn't exist,
* the zip file will be automatically created. If the path exist, it must be a file (not
* a directory) and it must be a valid zip file
*/
public ZipFileSystem(String zipFilePath) {
Map<String, String> zipFSproperties = new HashMap<>();
/* set create to true if you want to create a new ZIP file */
zipFSproperties.put("create", "true");
/* specify encoding to UTF-8 */
zipFSproperties.put("encoding", "UTF-8");
/* Locate File on disk for creation */
URI zipFileUri = new File(zipFilePath).toURI();
URI zipDisk = URI.create("jar:" + zipFileUri);
zipFSPath = Paths.get(zipFileUri);
if (!Files.exists(zipFSPath)) {
try {
createEmptyZipFile(zipFSPath);
} catch (Exception e ) {
System.out.println(e.getMessage());
}
} else {
if (Files.isDirectory(zipFSPath)) {
} else {
try {
// let's open it, which will verify if it's a valid zip file
ZipFile zipFile = new ZipFile(zipFilePath);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
try {
zipFS = FileSystems.newFileSystem(zipDisk, zipFSproperties);
} catch(Exception e) {
System.out.println(e.getMessage());
}
try {
listFiles(zipFS.getPath("/"));
} catch (IOException e) {
e.printStackTrace();
}
}
The correct way to open a zip from a Path - and create if not exists - is:
Path zip = Path.of("/Somepath/to/xyz.zip");
Map<String, String> env = Map.of(
"create", "true"
// other args here ...
);
try (FileSystem fs = FileSystems.newFileSystem(zip, env)) {
// code to read/update here
}
You have not closed any files or streams properly so your changes are probably not flushed back to the file system and will keep hold of file handles which block some operations.
Use try with resources for every operation which will manage the modifications to zip filesystem as well as closing each Stream<Path> from Files.find, and check other places such as createEmptyZipFile for the same problem:
try (FileSystem fs = ... ) {
try (Stream<Path> paths = Files.find(...) ) {
}
Files.copy( ... );
try (Stream<Path> paths = Files.find(...) ) {
}
}
The process cannot access the file because it is being used by another process
You have unnecessary code ZipFile zipFile = new ZipFile(zipFilePath) which tests the zip is valid and you do not call close(), so it will prevent the zip changes being written back. The check can safely be deleted (as FileSystems.newFileSystem does same) or must be wrapped in try() {} so that zipFile is closed before your edits to the zip filesystem.
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).
I am trying to implement program to zip and unzip a file. All I want to do is to zip a file (fileName.fileExtension) with name as fileName.zip and on unzipping change it again to fileName.fileExtension.
This is how I used to rename files or change its extension.
public static void modify(File file)
{
int index = file.getName().lastIndexOf(".");
//print filename
//System.out.println(file.getName().substring(0, index));
//print extension
//System.out.println(file.getName().substring(index));
String ext = file.getName().substring(index);
//use file.renameTo() to rename the file
file.renameTo(new File("Newname"+ext));
}
edit: John's method renames the file (keeping the extension). To change the extension do:
public static File changeExtension(File f, String newExtension) {
int i = f.getName().lastIndexOf('.');
String name = f.getName().substring(0,i);
return new File(f.getParent(), name + newExtension);
}
This changes only the last extension to a filename, i.e. the .gz part of archive.tar.gz. Therefore it works fine with Linux hidden files, for which the name starts with a .
This is quite safe because if getParent() returns null (i.e. in the event of the parent being the system root) it is "cast" to an empty String as the whole argument to the File constructor is evaluated first.
The only case where you will get a funny output is if you pass in a File representing the system root itself, in which case the null is prepended to the rest of the path string.
Try with:
File file = new File("fileName.zip"); // handler to your ZIP file
File file2 = new File("fileName.fileExtension"); // destination dir of your file
boolean success = file.renameTo(file2);
if (success) {
// File has been renamed
}
I would check, if the file has an extension before changing. The solution below works also with files without extension or multiple extensions
public File changeExtension(File file, String extension) {
String filename = file.getName();
if (filename.contains(".")) {
filename = filename.substring(0, filename.lastIndexOf('.'));
}
filename += "." + extension;
file.renameTo(new File(file.getParentFile(), filename));
return file;
}
#Test
public void test() {
assertThat(changeExtension(new File("C:/a/aaa.bbb.ccc"), "txt"),
is(new File("C:/a/aaa.bbb.txt")));
assertThat(changeExtension(new File("C:/a/test"), "txt"),
is(new File("C:/a/test.txt")));
}
By the same logic as mentioned #hsz, but instead simply use replacement:
File file = new File("fileName.fileExtension"); // creating object of File
String str = file.getPath().replace(".fileExtension", ".zip"); // replacing extension to another
file.renameTo(new File(str));
I want to avoid the new extension just happening to be in the path or filename itself. I like a combination of java.nio and apache StringFilenameUtils.
public void changeExtension(Path file, String extension) throws IOException {
String newFilename = FilenameUtils.removeExtension(file.toString()) + EXTENSION_SEPARATOR_STR + extension;
Files.move(file, Paths.get(newFilename, StandardCopyOption.REPLACE_EXISTING));
}
If you are using Kotlin you can use from this property of your file object:
file.nameWithoutExtension + "extension"
FilenameUtils.getFullPathNoEndSeparator(doc.getDocLoc()) + "/" +
FilenameUtils.getBaseName(doc.getDocLoc()) + ".xml"
My firend was working on a zipper in Java some 4 months back, I got this code from him.
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ZipFiles {
List<String> filesListInDir = new ArrayList<String>();
public static void main(String[] args) {
File file = new File("/Users/pankaj/sitemap.xml");
String zipFileName = "/Users/pankaj/sitemap.zip";
File dir = new File("/Users/pankaj/tmp");
String zipDirName = "/Users/pankaj/tmp.zip";
zipSingleFile(file, zipFileName);
ZipFiles zipFiles = new ZipFiles();
zipFiles.zipDirectory(dir, zipDirName);
}
/**
* This method zips the directory
* #param dir
* #param zipDirName
*/
private void zipDirectory(File dir, String zipDirName) {
try {
populateFilesList(dir);
//now zip files one by one
//create ZipOutputStream to write to the zip file
FileOutputStream fos = new FileOutputStream(zipDirName);
ZipOutputStream zos = new ZipOutputStream(fos);
for(String filePath : filesListInDir){
System.out.println("Zipping "+filePath);
//for ZipEntry we need to keep only relative file path, so we used substring on absolute path
ZipEntry ze = new ZipEntry(filePath.substring(dir.getAbsolutePath().length()+1, filePath.length()));
zos.putNextEntry(ze);
//read the file and write to ZipOutputStream
FileInputStream fis = new FileInputStream(filePath);
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) > 0) {
zos.write(buffer, 0, len);
}
zos.closeEntry();
fis.close();
}
zos.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* This method populates all the files in a directory to a List
* #param dir
* #throws IOException
*/
private void populateFilesList(File dir) throws IOException {
File[] files = dir.listFiles();
for(File file : files){
if(file.isFile())
filesListInDir.add(file.getAbsolutePath());
else
populateFilesList(file);
}
}
/**
* This method compresses the single file to zip format
* #param file
* #param zipFileName
*/
private static void zipSingleFile(File file, String zipFileName) {
try {
//create ZipOutputStream to write to the zip file
FileOutputStream fos = new FileOutputStream(zipFileName);
ZipOutputStream zos = new ZipOutputStream(fos);
//add a new Zip Entry to the ZipOutputStream
ZipEntry ze = new ZipEntry(file.getName());
zos.putNextEntry(ze);
//read the file and write to ZipOutputStream
FileInputStream fis = new FileInputStream(file);
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) > 0) {
zos.write(buffer, 0, len);
}
//Close the zip entry to write to zip file
zos.closeEntry();
//Close resources
zos.close();
fis.close();
fos.close();
System.out.println(file.getCanonicalPath()+" is zipped to "+zipFileName);
} catch (IOException e) {
e.printStackTrace();
}
}
}
I haven't tried it personally, but he and also some of my other friends told me that it works.
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