Unpacking / extracting resource from another JAR file - java

I have two jar files. Normally if I want to 'unpack' resource from my jar file I go for :
InputStream in = MyClass.class.getClassLoader().getResourceAsStream(name);
byte[] buffer = new byte[1024];
int read = -1;
File temp2 = new File(new File(System.getProperty("user.dir")), name);
FileOutputStream fos2 = new FileOutputStream(temp2);
while((read = in.read(buffer)) != -1) {
fos2.write(buffer, 0, read);
}
fos2.close();
in.close();
What If I would have another JAR files in the same directory? Can I access the second JAR file resources in simillar way? This second JAR is not runned so don't have own class loader. Is the only way to unzip this second JAR file?

I've used the below mentioned code to do the same kind of operation. It uses JarFile class to do the same.
/**
* Copies a directory from a jar file to an external directory.
*/
public static void copyResourcesToDirectory(JarFile fromJar, String jarDir, String destDir)
throws IOException {
for (Enumeration<JarEntry> entries = fromJar.entries(); entries.hasMoreElements();) {
JarEntry entry = entries.nextElement();
if (entry.getName().startsWith(jarDir + "/") && !entry.isDirectory()) {
File dest = new File(destDir + "/" + entry.getName().substring(jarDir.length() + 1));
File parent = dest.getParentFile();
if (parent != null) {
parent.mkdirs();
}
FileOutputStream out = new FileOutputStream(dest);
InputStream in = fromJar.getInputStream(entry);
try {
byte[] buffer = new byte[8 * 1024];
int s = 0;
while ((s = in.read(buffer)) > 0) {
out.write(buffer, 0, s);
}
} catch (IOException e) {
throw new IOException("Could not copy asset from jar file", e);
} finally {
try {
in.close();
} catch (IOException ignored) {}
try {
out.close();
} catch (IOException ignored) {}
}
}
}

If the other Jar is in your regular classpath then you can simply access the resource in that jar in the exact same way. If the Jar is just an file that's not on your classpath you will have to instead open it and extract the file with the JarFile and related classes. Note that Jar files are just special types of Zip files, so you can also access a Jar file with the ZipFile related classes

You can use URLClassLoader.
URLClassLoader classLoader = new URLClassLoader(new URL[]{new URL("path_to_file//myjar.jar")})
classLoader.loadClass("MyClass");//is requared
InputStream stream = classLoader.getResourceAsStream("myresource.properties");

Related

Java - Create Zip-file with multiple files from different locations with subfolders

I am trying to generate a zip file in Java, that contains several files of different types (e.g. images, fonts etc) that are lying in different locations. Furthermore I want the zip file to have subfolders where the files are put by their type (e.g. images should go to the images folder within the zip.
These are the files that I have (each can be in a different location):
index.html
img1.jpg
img2.jpg
font1.woff
font2.woff
style.css
custom.js
And this is how they should be in the zip file:
index.html
images/img1.jpg
images/img2.jpg
fonts/font1.woff
fonts/font2.woff
js/custom.js
css/styles.css
So far I have managed to take one file in a specific path and prompt the user for the output location. A zip-file will be generated with the file that is specified in the input. Here is the code I have so far:
JFrame parentFrame = new JFrame();
JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogTitle("Speicherort auswählen");
int userSelection = fileChooser.showSaveDialog(parentFrame);
String pathToFile;
if (userSelection == JFileChooser.APPROVE_OPTION) {
File fileToSave = fileChooser.getSelectedFile();
print(fileToSave.getAbsolutePath());
pathToFile = fileToSave.getAbsolutePath();
}
pathToFile = pathToFile.replace("\\", "/");
String outFileName = pathToFile;
String inFileName = "C:/Users/asoares/Desktop/mobio_export_test/index.html";
ZipOutputStream zos = null;
FileInputStream fis = null;
try {
zos = new ZipOutputStream(new FileOutputStream(outFileName));
fis = new FileInputStream(inFileName);
zos.putNextEntry(new ZipEntry(new File(inFileName).getName()));
int len;
byte[] buffer = new byte[2048];
while((len = fis.read(buffer, 0, buffer.length)) > 0) {
zos.write(buffer, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fis != null){
try {
fis.close();
} catch (IOException e) {}
}
if(zos != null){
try {
zos.closeEntry();
zos.close();
} catch (IOException e) {}
}
}
I would be really glad if someone can help me!!!
It should work like this.
The zip directory name should at best be created by another method (there are more image types than jpg :)).
public static Path zip(List<Path> files, Path zipFileTarget) throws IOException {
try (FileOutputStream fos = new FileOutputStream(zipFileTarget.toFile());
ZipOutputStream zos = new ZipOutputStream(fos)) {
if (!Files.exists(zipFileTarget))
Files.createFile(zipFileTarget);
createEntries(files, zos);
zos.close();
return zipFileTarget;
}
}
private static List<String> createEntries(List<Path> files, ZipOutputStream zos) throws IOException {
List<String> zippedFiles = new ArrayList<>();
Matcher matcherFileExt = Pattern.compile("^.*\\.([^.]+)$").matcher("");
for (Path f : files) {
if (Files.isRegularFile(f)) {
String fileName = f.getFileName().toString();
String fileExt = matcherFileExt.reset(fileName).matches()
? matcherFileExt.replaceAll("$1")
: "unknown";
// You should determine the dir name with a more sophisticated
// approach.
String dir;
if (fileExt.equals("jpg")) dir = "images";
else if (fileExt.equals("woff")) dir = "fonts";
else dir = fileExt;
zos.putNextEntry(new ZipEntry(dir + "/" + fileName));
Files.copy(f, zos);
zippedFiles.add(fileName);
}
}
return zippedFiles;
}
Edit: this approach works with java 1.7+. You can easily convert a File object to a Path object by calling its toPath() method.

How to delete a specific File/Folder from a jar pragmatically in java

How to delete a specific File/Folder from a jar pragmatically in java.
I have a jar ABC.jar contains files, folder and another jars say child.jar.
under child.jar I want to delete a specific file. How can I do? so that my ABC.jar structure remains same.
Any help will be appreciate.
Thanks in Advance.
As answered by #icza we have to iterate through original jar file and deleting the entry we don't want.
Here is the java code you can refer.
public static void main(String[] args) throws IOException {
String jarName = args[0];
String fileName = args[1];
// Create file descriptors for the jar and a temp jar.
File jarFile = new File(jarName);
File tempJarFile = new File(jarName + ".tmp");
// Open the jar file.
JarFile jar = new JarFile(jarFile);
System.out.println(jarName + " opened.");
// Initialize a flag that will indicate that the jar was updated.
boolean jarUpdated = false;
try {
// Create a temp jar file with no manifest. (The manifest will
// be copied when the entries are copied.)
Manifest jarManifest = jar.getManifest();
JarOutputStream tempJar =
new JarOutputStream(new FileOutputStream(tempJarFile));
// Allocate a buffer for reading entry data.
byte[] buffer = new byte[1024];
int bytesRead;
try {
// Open the given file.
FileInputStream file = new FileInputStream(fileName);
try {
// Create a jar entry and add it to the temp jar.
JarEntry entry = new JarEntry(fileName);
tempJar.putNextEntry(entry);
// Read the file and write it to the jar.
while ((bytesRead = file.read(buffer)) != -1) {
tempJar.write(buffer, 0, bytesRead);
}
System.out.println(entry.getName() + " added.");
}
finally {
file.close();
}
// Loop through the jar entries and add them to the temp jar,
// skipping the entry that was added to the temp jar already.
for (Enumeration entries = jar.entries(); entries.hasMoreElements(); ) {
// Get the next entry.
JarEntry entry = (JarEntry) entries.nextElement();
// If the entry has not been added already, add it.
if (! entry.getName().equals(fileName)) {
// Get an input stream for the entry.
InputStream entryStream = jar.getInputStream(entry);
// Read the entry and write it to the temp jar.
tempJar.putNextEntry(entry);
while ((bytesRead = entryStream.read(buffer)) != -1) {
tempJar.write(buffer, 0, bytesRead);
}
}
}
jarUpdated = true;
}
catch (Exception ex) {
System.out.println(ex);
// Add a stub entry here, so that the jar will close without an
// exception.
tempJar.putNextEntry(new JarEntry("stub"));
}
finally {
tempJar.close();
}
}
finally {
jar.close();
System.out.println(jarName + " closed.");
// If the jar was not updated, delete the temp jar file.
if (! jarUpdated) {
tempJarFile.delete();
}
}
// If the jar was updated, delete the original jar file and rename the
// temp jar file to the original name.
if (jarUpdated) {
jarFile.delete();
tempJarFile.renameTo(jarFile);
System.out.println(jarName + " updated.");
}
}
Sun/Oracle bug database asks for this feature to be implemented in the java api.
Check here
There is a simple way to delete a file from a JAR by executing a command shell during runtime.
Executing the command below does the job:
Runtime.getRuntime().exec("zip -d path\my.jar some_file.txt");
Where path is the absolute path to the jar file and some_file.txt is the file to be deleted. In this example the file resides on the main folder of the jar. You may need to provide its relative path if the file resides on a different folder
The path of the jar itself you may know ahead or can find it relatively based on the class you are executing the command shell:
String path = SomeClass.class.getProtectionDomain().getCodeSource().getLocation().getPath();
You can trace the execution of the process by listening to the available streams:
Process p = Runtime.getRuntime().exec("zip -d path\my.jar some_file.txt");
BufferedReader reader =
new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = "";
StringBuilder sb = new StringBuilder();
while ((line = reader.readLine())!= null) {
sb.append(line + "\n");
System.out.println(line);
}
System.out.println(sb.toString());
Jar/zip files are not editable. You can't delete an entry from a jar/zip file.
What you can do is "re-create" the jar file like this: start a new jar file, iterate over the entries of the current jar file, and add those entries to the new jar file which you do not want to delete.
Theoretically it would be possible to delete an entry like this (but the standard Java lib is not suitable for this, meaning the ZipFile, ZipInputStream, JarFile, JarInputStream classes):
Entries in a jar/zip file are sequencial. Each entry has a header with info about the entry (which may be a file or folder entry). This header also contains the byte-length of the entry. So you could iterate over the entries sequencially, and if you encounter an entry which you want to delete (and you know its size from its header), you could copy the remaining of the file (content after this entry) to the begining of the current entry (obviously the file size should be trunced by the length of the current / deleted entry).
Or your other options include not doing this via Java but using an external tool like the zip -d command itself.
public static void filterJar(Path jarInFileName, String skipRegex, Path jarOutFileName) throws IOException {
ZipEntry entry;
ZipInputStream zis = null;
JarOutputStream os = null;
FileInputStream is = null;
try {
is = new FileInputStream(jarInFileName.toFile());
Pattern pattern = Pattern.compile(skipRegex);
zis = new ZipInputStream(is);
os = new JarOutputStream(new FileOutputStream(jarOutFileName.toFile()));
while ((entry = zis.getNextEntry()) != null) {
if (pattern.matcher(entry.getName()).matches()) continue;
os.putNextEntry(entry);
if (!entry.isDirectory()) {
byte[] bytes = toBytes(zis);
os.write(bytes);
}
}
}
catch (Exception ex) {
throw new IOException("unable to filter jar:" + ex.getMessage());
}
finally {
closeQuietly(is);
closeQuietly(os);
}
}
public static void closeQuietly(final Closeable closeable) {
try {
if (closeable != null) {
closeable.close();
}
}
catch (final Exception e) {}
}
public static byte[] toBytes(InputStream aInput) throws IOException {
byte[] bucket = new byte[5 * 1024];
ByteArrayOutputStream result = null;
result = new ByteArrayOutputStream(bucket.length);
int bytesRead = 0;
while (bytesRead != -1) {
bytesRead = aInput.read(bucket);
if (bytesRead > 0) {
result.write(bucket, 0, bytesRead);
}
}
return result.toByteArray();
}
public static void main(String[] args) throws IOException {
filterJar(Paths.get("./old.jar"), "BOOT-INF/lib.*", Paths.get("./new.jar"));
}
You can use the Zip File System to treat zip/jar files as a file system. This will allow you to edit, delete, and add files to the jar file.
See Appending files to a zip file with Java

Extracting zip file into a folder throws "Invalid entry size (expected 46284 but got 46285 bytes)" for one of the entry

When I am trying to extract the zip file into a folder as per the below code, for one of the entry (A text File) getting an error as "Invalid entry size (expected 46284 but got 46285 bytes)" and my extraction is stopping abruptly. My zip file contains around 12 text files and 20 TIF files. It is encountering the problem for the text file and is not able to proceed further as it is coming into the Catch block.
I face this problem only in Production Server which is running on Unix and there is no problem with the other servers(Dev, Test, UAT).
We are getting the zip into the servers path through an external team who does the file transfer and then my code starts working to extract the zip file.
...
int BUFFER = 2048;
java.io.BufferedOutputStream dest = null;
String ZipExtractDir = "/y34/ToBeProcessed/";
java.io.File MyDirectory = new java.io.File(ZipExtractDir);
MyDirectory.mkdir();
ZipFilePath = "/y34/work_ZipResults/Test.zip";
// Creating fileinputstream for zip file
java.io.FileInputStream fis = new java.io.FileInputStream(ZipFilePath);
// Creating zipinputstream for using fileinputstream
java.util.zip.ZipInputStream zis = new java.util.zip.ZipInputStream(new java.io.BufferedInputStream(fis));
java.util.zip.ZipEntry entry;
while ((entry = zis.getNextEntry()) != null)
{
int count;
byte data[] = new byte[BUFFER];
java.io.File f = new java.io.File(ZipExtractDir + "/" + entry.getName());
// write the files to the directory created above
java.io.FileOutputStream fos = new java.io.FileOutputStream(ZipExtractDir + "/" + entry.getName());
dest = new java.io.BufferedOutputStream(fos, BUFFER);
while ((count = zis.read(data, 0, BUFFER)) != -1)
{
dest.write(data, 0, count);
}
dest.flush();
dest.close();
}
zis.close();
zis.closeEntry();
}
catch (Exception Ex)
{
System.Out.Println("Exception in \"ExtractZIPFiles\"---- " + Ex.getMessage());
}
I can't understand the problem you're meeting, but here is the method I use to unzip an archive:
public static void unzip(File zip, File extractTo) throws IOException {
ZipFile archive = new ZipFile(zip);
Enumeration<? extends ZipEntry> e = archive.entries();
while (e.hasMoreElements()) {
ZipEntry entry = e.nextElement();
File file = new File(extractTo, entry.getName());
if (entry.isDirectory()) {
file.mkdirs();
} else {
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
InputStream in = archive.getInputStream(entry);
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
IOUtils.copy(in, out);
in.close();
out.close();
}
}
}
Calling:
File zip = new File("/path/to/my/file.zip");
File extractTo = new File("/path/to/my/destination/folder");
unzip(zip, extractTo);
I never met any issue with the code above, so I hope that could help you.
Off the top of my head, I could think of these reasons:
There could be problem with the encoding of the text file.
The file needs to be read/transferred in "binary" mode.
There could be an issue with the line ending \n or \r\n
The file could simply be corrupt. Try opening the file with a zip utility.

How does one go about finding a specific directory out of a jar/zip file in java?

I have been working on this for quite a few hours. I can't seem to find the issue to this problem. Essentially what I have is this:
I have a jar, let's call it "a.jar"
I need to get the directory "z" and it's contents from "a.jar", but "z" isn't in the root directory of "a.jar".
"z" is in "/x/y/" and "/x/y/" is in "a.jar", so it looks like this:
"a.jar/x/y/z/"
I hope that's a decent explanation. By the way, "a.jar" is what everything is running out of, so its in the class path obviously.
Basically for each ZipEntry you have to check if it isDirectory() and parse that also.
Checkout this link:
http://www.javaworld.com/javaworld/javatips/jw-javatip49.html
LE:
Here is a complete example that extracts the files from the jar, and if you specify a specific path it will extract only that folder:
public void doUnzip(String inputZip, String destinationDirectory, String specificPath)
throws IOException {
int BUFFER = 2048;
File sourceZipFile = new File(inputZip);
File unzipDestinationDirectory = new File(destinationDirectory);
unzipDestinationDirectory.mkdir();
ZipFile zipFile;
// Open Zip file for reading
zipFile = new ZipFile(sourceZipFile, ZipFile.OPEN_READ);
// Create an enumeration of the entries in the zip file
Enumeration<?> zipFileEntries = zipFile.entries();
// Process each entry
while (zipFileEntries.hasMoreElements()) {
// grab a zip file entry
ZipEntry entry = (ZipEntry) zipFileEntries.nextElement();
if(specificPath != null){
if(entry.getName().startsWith(specificPath) == false)
continue;
}
File destFile = new File(unzipDestinationDirectory, entry.getName());
// create the parent directory structure if needed
destFile.getParentFile().mkdirs();
try {
// extract file if not a directory
if (!entry.isDirectory()) {
BufferedInputStream is = new BufferedInputStream(
zipFile.getInputStream(entry));
// establish buffer for writing file
byte data[] = new byte[BUFFER];
// write the current file to disk
FileOutputStream fos = new FileOutputStream(destFile);
BufferedOutputStream dest = new BufferedOutputStream(fos,
BUFFER);
// read and write until last byte is encountered
for (int bytesRead; (bytesRead = is.read(data, 0, BUFFER)) != -1;) {
dest.write(data, 0, bytesRead);
}
dest.flush();
dest.close();
is.close();
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
zipFile.close();
}
public static void main(String[] args) {
Unzip unzip = new Unzip();
try {
unzip.doUnzip("test.jar", "output", "x/y/z");
} catch (IOException e) {
e.printStackTrace();
}
}
..(ZipEntry), but they don't work very well with sub-directories.
They work just fine. Iterate the entries and simply check the path equates to that sub-directory. If it does, add it to a list (or process it, whatever).

How should I extract compressed folders in java?

I am using the following code to extract a zip file in Java.
import java.io.*;
import java.util.zip.*;
class testZipFiles
{
public static void main(String[] args)
{
try
{
String filename = "C:\\zip\\includes.zip";
testZipFiles list = new testZipFiles( );
list.getZipFiles(filename);
}
catch (Exception e)
{
e.printStackTrace();
}
}
public void getZipFiles(String filename)
{
try
{
String destinationname = "c:\\zip\\";
byte[] buf = new byte[1024];
ZipInputStream zipinputstream = null;
ZipEntry zipentry;
zipinputstream = new ZipInputStream(
new FileInputStream(filename));
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 (Exception e)
{
e.printStackTrace();
}
}
}
Obviously this will not extract a folder tree because of the break statement. I tried to use recursion to process a folder tree but failed. Could someone show me how to improve this code to handle a folder tree instead of a compressed single level folder.
You can use File.mkdirs() to create folders. Try changing your method like this:
public static void getZipFiles(String filename) {
try {
String destinationname = "c:\\zip\\";
byte[] buf = new byte[1024];
ZipInputStream zipinputstream = null;
ZipEntry zipentry;
zipinputstream = new ZipInputStream(
new FileInputStream(filename));
zipentry = zipinputstream.getNextEntry();
while (zipentry != null) {
//for each entry to be extracted
String entryName = destinationname + zipentry.getName();
entryName = entryName.replace('/', File.separatorChar);
entryName = entryName.replace('\\', File.separatorChar);
System.out.println("entryname " + entryName);
int n;
FileOutputStream fileoutputstream;
File newFile = new File(entryName);
if (zipentry.isDirectory()) {
if (!newFile.mkdirs()) {
break;
}
zipentry = zipinputstream.getNextEntry();
continue;
}
fileoutputstream = new FileOutputStream(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 (Exception e) {
e.printStackTrace();
}
}
Another option is commons-compress, for which there is sample code on the site linked above.
I needed to do this because of an API which I was using required a File parameter, which you can't get from a resource in a JAR.
I found that the answer from #Emre didn't work correctly. For some reason ZipEntry skipped a few files in the JAR (no apparent pattern to this). I fixed this by using JarEntry instead. There is also a bug in the above code where the file in the zip entry could be enumerated before the directory is, which causes an exception because the directory hasn't been created yet.
Note that the below code depends on Apache Commons utility classes.
/**
*
* Extract a directory in a JAR on the classpath to an output folder.
*
* Note: User's responsibility to ensure that the files are actually in a JAR.
* The way that I do this is to get the URI with
* URI url = getClass().getResource("/myresource").toURI();
* and then if url.isOpaque() we are in a JAR. There may be a more reliable
* way however, please edit this answer if you know of one.
*
* #param classInJar A class in the JAR file which is on the classpath
* #param resourceDirectory Path to resource directory in JAR
* #param outputDirectory Directory to write to
* #return String containing the path to the folder in the outputDirectory
* #throws IOException
*/
private static String extractDirectoryFromClasspathJAR(Class<?> classInJar, String resourceDirectory, String outputDirectory)
throws IOException {
resourceDirectory = StringUtils.strip(resourceDirectory, "\\/") + File.separator;
URL jar = classInJar.getProtectionDomain().getCodeSource().getLocation();
//Note: If you want to extract from a named JAR, remove the above
//line and replace "jar.getFile()" below with the path to the JAR.
JarFile jarFile = new JarFile(new File(jar.getFile()));
byte[] buf = new byte[1024];
Enumeration<JarEntry> jarEntries = jarFile.entries();
while (jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
if (jarEntry.isDirectory() || !jarEntry.getName().startsWith(resourceDirectory)) {
continue;
}
String outputFileName = FilenameUtils.concat(outputDirectory, jarEntry.getName());
//Create directories if they don't exist
new File(FilenameUtils.getFullPath(outputFileName)).mkdirs();
//Write file
FileOutputStream fileOutputStream = new FileOutputStream(outputFileName);
int n;
InputStream is = jarFile.getInputStream(jarEntry);
while ((n = is.read(buf, 0, 1024)) > -1) {
fileOutputStream.write(buf, 0, n);
}
is.close();
fileOutputStream.close();
}
jarFile.close();
String fullPath = FilenameUtils.concat(outputDirectory, resourceDirectory);
return fullPath;
}

Categories

Resources