Programmatically copy class files from within a jar to another jar - java

I have an application as jar file (that includes a bunch of classes.)
Within the running of the application I want to create a new Jar file as an output.
I can do this no problem if I get the individual classes from the current jar as a resource, write them to disk as respective classes, then when I create the new jar file read them all in and create the new JAR.
However, I don't think this is smart nor efficient... and that I'm probably overlooking something really obvious...
I'm hoping I can simply copy the resource over in memory without writing the files out again that I need.
Today I do this:
File[] list = new File {
new File("Class1.class");
new File("Class2.class");
Manifest m = new Manifest();
m.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
m.getMainAttributes().put(Attributes.Name.MAIN_CLASS, main_class);
JarOutputStream target = new JarOutputStream(new FileOutputStream(fout), manifest);
for(int n=0;n<list.length;n++) {
add(list,target)
}
target.close();
The Function add is per below:
private static void add(File source, JarOutputStream target) throws IOException
{
BufferedInputStream in = null;
try {
JarEntry entry = new JarEntry(source.getPath().replace("\\", "/"));
entry.setTime(source.lastModified());
target.putNextEntry(entry);
in = new BufferedInputStream(new FileInputStream(source));
byte[] buffer = new byte[1024];
while (true) {
int count = in.read(buffer);
if(count == -1)
break;
target.write(buffer, 0, count);
}
target.closeEntry();
} finally {
if (in != null)
in.close();
}
}
Now this all works fine... but it relies on the fact that I'm reading the relevant class files from the disk. What I want to do is read the relevant class files from a resource jar file... AND THEN write the class files I want back out to a completely new jar file.
Ideally what I want to do is this:
Within the main application:
InputStream[] stream = new InputStream[] {
MyClass.class.getResourceAsStream("Class1.class"),
MyClass.class.getResourceAsStream("Class2.class")};
Then take that stream and write that back out into a new Jar file.
So making the add function above now be an add(InputStream, JarOutputStream target).
Am I on the right track or is this making a mountain out of a mole hill?
thanks in advance for any help.

Related

The file gets smaller after reading the jar package and writing it to another file

package com.example.demo.Util;
public class Test {
static HashMap<String,String> map = new HashMap<>();
public static void main(String[] args) throws IOException {
String data = "12j3h1i7tsa7sgdajk123y8asd: 88888";
File jarFile = new File(new Test().getJarPath());
File tempJar = upJarFile(jarFile, "BOOT-INF/classes/application.properties", data);
}
public static File upJarFile(File originalJarFile, String editFilePath, String content) throws IOException {
File tempFile = File.createTempFile("temp", ".jar");
JarFile jarFile = new JarFile(originalJarFile);
Enumeration<JarEntry> entries = jarFile.entries();
System.out.println("before:"+ originalJarFile.length());
JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(tempFile));
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
jarOutputStream.putNextEntry(jarEntry);
map.put(jarEntry.getName(), String.valueOf(jarEntry.getSize()));
jarOutputStream.write(new Test().inputStreamToByteArray(jarFile.getInputStream(jarEntry)));
}
jarOutputStream.finish();
jarOutputStream.close();
System.out.println(tempFile.getPath());
System.out.println("after:" + tempFile.length());
return tempFile;
}
public String getJarPath() {
String path1 = System.getProperty("user.dir");
File file = new File(path1 + "/target/");
String jarFile = null;
for (File file1 : file.listFiles()) {
if (file1.getName().endsWith(".jar")) {
jarFile = file1.getPath();
break;
}
}
return jarFile;
}
public byte[] inputStreamToByteArray(InputStream inputStream) {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int num;
while ((num = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, num);
}
byteArrayOutputStream.flush();
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return new byte[]{};
}
}
As shown in the code above,I just turn the incoming jar packages into streams and write them one by one,But it got smaller when I tested the size of the input package and the size of the output temporary package(before:49651057-->after:49647985)
What could be causing this difference?
This can happen due to a number of reasons:
The original JAR file was created with a compression level that is not as high as the default compression level, so the JAR file that you create (with default compression) achieves better compression, and therefore it is smaller. You can verify this by opening both the original and the result JAR files with a ZIP utility (e.g. 7Zip) and examining their checksums and their compressed sizes. If the checksums are identical, but the compressed sizes differ, then the difference is simply due to better compression.
The original JAR file contains unused data. This can happen when sloppy archive creation software updates an archive by appending to it instead of rewriting it from scratch. You can verify this by opening the original ZIP archive with a ZIP utility (e.g. 7Zip) and saving it under a new filename. If the new file is smaller, then the original file contained some unused data.
The original JAR file contains files in subdirectories, which you are not checking. Thus, your output JAR file does not contain all of the files in the original. To fix this, you need to check each entry with jarEntry.isDirectory() and if so, recurse.

How to copy folder's out of resources from jar both in runtime and dev-env?

I am currently making a game which have levels which are pre-made and i currently store them in resources. I want a solution to how i can extract a folder out of a jar in production and development environment.
I have tried copying the folder by using the given method below and pass the src as File defaultWorld = new File(GameData.class.getClassLoader().getResource("worlds/").getFile());
and destination as private static File worldsDir = new File("run/worlds");
public static void copyFolder(File src, File dest) {
try {
if (src.isDirectory()) {
if (!dest.exists()) {
dest.mkdir();
}
String[] files = src.list();
for (String file : files) {
copyFolder(new File(src, file), new File(dest, file));
}
} else {
try (InputStream in = new FileInputStream(src)) {
try (OutputStream out = new FileOutputStream(dest)) {
byte[] buffer = new byte[1024];
int length;
//copy the file content in bytes
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
I expected the above method to work on both dev and production env but it throws FileNotFoundException when opening file output stream.
You cannot list resources in a jar.
Any workarounds you’re thinking of, are unreliable.
Never call the getFile() method of URL. It does not return a valid file name; it just returns the path and query portions of the URL, with any percent-escapes intact. Furthermore, jar entries are not file: URLs, so a resource path can never be a valid file name when it refers to a jar entry.
The only way to list things in a jar file is by iterating through all jar entries, but you aren’t even guaranteed to have access to your jar, because ClassLoaders are not guaranteed to be URLClassLoaders, and in general are not guaranteed to use jar: URLs.
You can’t even rely on MyApplication.class.getProtectionDomain().getCodeSource(), because getCodeSource() can return null.
If you want to copy multiple files from your jar, here are some reliable ways to do it:
Hard code the list of resources you plan to copy. It’s your application, so you know what files you’re putting in the jar.
Keep a single text file in your jar which contains a list of resource paths to copy.
Store your resources in a single zip archive which is embedded in your jar, and extract it yourself with a ZipInputStream which wraps MyApplication.class.getResourceAsStream.

Getting strange structure file when zipping a directory using Java

I wanted to zip a directory with files and subdirectories in it. I did this and worked fine but I am getting and unusual and curious file structure (At least I see it that way).
This is the created file: When I click on it, I see an "empty" directory like this: but when I unzip this I see this file structure (Not all the names are exacly as they are showed in the image below):
|mantenimiento
|Carpeta_A
|File1.txt
|File2.txt
|Carpeta_B
|Sub_carpetaB
|SubfileB.txt
|Subfile1B.txt
|Subfile2B.txt
|File12.txt
My problem somehow is that the folder "mantenimiento" is where I am zippping from (the directory which I want to zip) and I dont want it to be there, so when I unzip the just created .zip file I want it with this file structure (which are the files and directories inside "mantenimiento" directory): and the other thing is when I click on the .zip file I want to see the files and directories just like the image showed above.
I dont know what's wrong with my code, I have searched but haven't found a reference to what my problem might be.
Here's my code:
private void zipFiles( List<File> files, String directory) throws IOException
{
ZipOutputStream zos = null;
ZipEntry zipEntry = null;
FileInputStream fin = null;
FileOutputStream fos = null;
BufferedInputStream in = null;
String zipFileName = getZipFileName();
try
{
fos = new FileOutputStream( File.separatorChar + zipFileName + EXTENSION );
zos = new ZipOutputStream(fos);
byte[] buf = new byte[1024];
int len;
for(File file : files)
{
zipEntry = new ZipEntry(file.toString());
fin = new FileInputStream(file);
in = new BufferedInputStream(fin);
zos.putNextEntry(zipEntry);
while ((len = in.read(buf)) >= 0)
{
zos.write(buf, 0, len);
}
}
}
catch(Exception e)
{
System.err.println("No fue posible zipear los archivos");
e.printStackTrace();
}
finally
{
in.close();
zos.closeEntry();
zos.close();
}
}
Hope you guys can give me a hint about what I am doing wrong or what I am missing.
Thanks a lot.
Btw, the directory i am giving to the method is never used. The other parameter i am giving is a list of files which contains all the files and directories from the C:\mantenimiento directory.
I once had a problem with windows and zip files, where the created zip did not contain the entries for the folders (i.e. /, /Carpeta_A etc) only the file entries. Try adding ZipEntries for the folders without streaming content.
But as alternative to the somewhat bulky Zip API of Java you could use Filesystem (since Java7) instead. The following example is for Java8 (lambda):
//Path pathToZip = Paths.get("path/to/your/folder");
//Path zipFile = Paths.get("file.zip");
public Path zipPath(Path pathToZip, Path zipFile) {
Map<String, String> env = new HashMap<String, String>() {{
put("create", "true");
}};
try (FileSystem zipFs = FileSystems.newFileSystem(URI.create("jar:" + zipFile.toUri()), env)) {
Path root = zipFs.getPath("/");
Files.walk(pathToZip).forEach(path -> zip(root, path));
}
}
private static void zip(final Path zipRoot, final Path currentPath) {
Path entryPath = zipRoot.resolve(currentPath.toString());
try {
Files.createDirectories(entryPath.getParent());
Files.copy(currentPath, entryPath);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

How to download a file from the internet using Java

Hi I am trying to write some code in my program so I can grab a file from the internet but it seems that is not working. Can someone give me some advice please ? Here is my code. In this case I try to download an mp3 file from the last.fm website, my code runs perfectly fine but when I open my downloads directory the file is not there. Any idea ?
public class download {
public static void main(String[] args) throws IOException {
String fileName = "Death Grips - Get Got.mp3";
URL link = new URL("http://www.last.fm/music/+free-music-downloads");
InputStream in = new BufferedInputStream(link.openStream());
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int n = 0;
while (-1!=(n=in.read(buf)))
{
out.write(buf, 0, n);
}
out.close();
in.close();
byte[] response = out.toByteArray();
FileOutputStream fos = new FileOutputStream(fileName);
fos.write(response);
fos.close();
System.out.println("Finished");
}
}
Every executing program has a current working directory. Often times, it is the directory where the executable lives (if it was launched in a "normal" way).
Since you didn't specify a path (in fileName), the file will be saved with that name in the current working directory.
If you want the file to be saved in your downloads directory, specify the full path. E.g.
String fileName = "C:\\Users\\YOUR_USERNAME\\Downloads\\Death Grips - Get Got.mp3";
Note how I've escaped the backslashes. Also note that there are methods for joining paths in Java. There is a way to get the current working directory in Java.

How to copy files out of the currently running jar

I have a .jar that has two .dll files that it is dependent on. I would like to know if there is any way for me to copy these files from within the .jar to a users temp folder at runtime. here is the current code that I have (edited to just one .dll load to reduce question size):
public String tempDir = System.getProperty("java.io.tmpdir");
public String workingDir = dllInstall.class.getProtectionDomain().getCodeSource().getLocation().getPath();
public boolean installDLL() throws UnsupportedEncodingException {
try {
String decodedPath = URLDecoder.decode(workingDir, "UTF-8");
InputStream fileInStream = null;
OutputStream fileOutStream = null;
File fileIn = new File(decodedPath + "\\loadAtRuntime.dll");
File fileOut = new File(tempDir + "loadAtRuntime.dll");
fileInStream = new FileInputStream(fileIn);
fileOutStream = new FileOutputStream(fileOut);
byte[] bufferJNI = new byte[8192000013370000];
int lengthFileIn;
while ((lengthFileIn = fileInStream.read(bufferJNI)) > 0) {
fileOutStream.write(bufferJNI, 0, lengthFileIn);
}
//close all steams
} catch (IOException e) {
e.printStackTrace();
return false;
} catch (UnsupportedEncodingException e) {
System.out.println(e);
return false;
}
My main problem is getting the .dll files out of the jar at runtime. Any way to retrieve the path from within the .jar would be helpful.
Thanks in advance.
Since your dlls are bundeled inside your jar file you could just try to acasses them as resources using ClassLoader#getResourceAsStream and write them as binary files any where you want on the hard drive.
Here is some sample code:
InputStream ddlStream = <SomeClassInsideTheSameJar>.class
.getClassLoader().getResourceAsStream("some/pack/age/somelib.dll");
try (FileOutputStream fos = new FileOutputStream("somelib.dll");){
byte[] buf = new byte[2048];
int r;
while(-1 != (r = ddlStream.read(buf))) {
fos.write(buf, 0, r);
}
}
The code above will extract the dll located in the package some.pack.age to the current working directory.
Use a class loader that is able to locate resources in this JAR file. Either you can use the class loader of a class as Peter Lawrey suggested, or you can also create a URLClassLoader with the URL to that JAR.
Once you have that class loader you can retrieve a byte input stream with ClassLoader.getResourceAsStream. On the other hand you just create a FileOutputStream for the file you want to create.
The last step then is to copy all bytes from the input stream to the output stream, as you already did in your code example.
Use myClass.getClassLoader().getResourceAsStream("loadAtRuntime.dll"); and you will be able to find and copy DLLs in the JAR. You should pick a class which will also be in the same JAR.

Categories

Resources