Read contents of jar file given specific path - java

I have a jar file named "san.jar" with various folders like "classes", "resources", etc.,
Say for e.g i have a folder structure like "resources/assets/images" under which there are various images which I do not have any information about them like name of the images or number of images under the folder as the jar file is private and I am not allowed to unzip the jar.
OBJECTIVE: I need to get all the files under the given path without iterating over the whole jar file.
Right now what I am doing is iterating through each and every entry and whenever i come across .jpg file, I perform some operation.
Here for reading just the "resources/assets/images", I am iterating through the whole jarfile.
JarFile jarFile = new JarFile("san.jar");
for(Enumeration em = jarFile.entries(); em.hasMoreElements();) {
String s= em.nextElement().toString();
if(s.contains("jpg")){
//do something
}
}
Right now what I am doing is iterating through each and every entry and whenever i come across .jpg file, I perform some operation.
Here for reading just the "resources/assets/images", I am iterating through the whole jarfile.

With Java 8 and filesystems it is pretty easy now,
Path myjar;
try (FileSystem jarfs = FileSystems.newFileSystem(myjar, null)) {
Files.find(jarfs.getPath("resources", "assets", "images"),
1,
(path, attr) -> path.endsWith(".jpg"),
FileVisitOption.FOLLOW_LINKS).forEach(path -> {
//do something with the image.
});
}
Files.find will only search the provided path up the the desired depth.

This code works your purpose
JarFile jarFile = new JarFile("my.jar");
for(Enumeration<JarEntry> em = jarFile.entries(); em.hasMoreElements();) {
String s= em.nextElement().toString();
if(s.startsWith(("path/to/images/directory/"))){
ZipEntry entry = jarFile.getEntry(s);
String fileName = s.substring(s.lastIndexOf("/")+1, s.length());
if(fileName.endsWith(".jpg")){
InputStream inStream= jarFile.getInputStream(entry);
OutputStream out = new FileOutputStream(fileName);
int c;
while ((c = inStream.read()) != -1){
out.write(c);
}
inStream.close();
out.close();
System.out.println(2);
}
}
}
jarFile.close();

This can be done much more concisely with a regex... It will also work when jpg files have upper case extension JPG.
JarFile jarFile = new JarFile("my.jar");
Pattern pattern = Pattern.compile("resources/assets/images/([^/]+)\\.jpg",
Pattern.CASE_INSENSITIVE);
for (Enumeration<JarEntry> em = jarFile.entries(); em
.hasMoreElements();) {
JarEntry entry = em.nextElement();
if (pattern.matcher(entry.getName()).find()) {
BufferedImage image = ImageIO.read(jarFile
.getInputStream(entry));
System.out.println(image.getWidth() + " "
+ image.getHeight());
}
}
jarFile.close();

Related

Reading Directory Contents From a JAR file

I am trying to write a code in a webapp, where I have a JAR file in my classpath. The objective is to check if the directory exists in the JAR. If yes, I need to save the all the contents of the files inside the JAR's directory in a HashMap<String, String>. The Key being the file name and the value being the contents of each file.
File directory = new File(getClass().getClassLoader().getResource(directoryPath).getPath());
System.out.println("PATH IS: " + directory.getPath());
// Check if dirPth exists and is a valid directory
if (!directory.isDirectory()) {
throw new AccessException("Directory \"" + directoryPath + "\" not valid");
}
// Obtain a list of all files under the dirPath
File [] fileList = directory.listFiles();
for (File file : fileList) {
if (file.isFile()) {
// Read the file
BufferedReader br = new BufferedReader(new FileReader(file));
String line = null;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line);
}
br.close();
// Store the file data in the hash
entry.put(file.getName(), sb.toString);
}
}
The output of the direcotry.getPath() is:
file:\H:\apache-tomcat-9.0.27\lib\myConfigurationFiles.jar!\META-INF\Maintenance\xmlFiles\secondary
which is the right folder I am looking for.
Here the Map object is the "entry".
Now I am not sure why direcotry.isDirectory() returns false. Shouldn't it return true?
Now since its not crossing the first exception. I have no idea how it will behave after that. Any help would be appreciated.
getClass() is the wrong approach for jobs like this; it breaks if anybody subclasses. The proper way is to use MyClassName.class instead.
getClassLoader().getResource() is also the wrong approach; this breaks in exotic but possible cases where getClassLoader() returns null. Just use getResource and slightly change the path (add a leading slash, or, write the path relative to your class file).
You're turning the string file:\H:\apache-tomcat-9.0.27\lib\myConfigurationFiles.jar!\META-INF\Maintenance\xmlFiles\secondary into a filename and then asking if it is a directory. Of course it isn't; that isn't even a file. You need to do some string manipulation to extract the actual file out of it: You want just H:\apache-tomcat-9.0.27\lib\myConfigurationFiles.jar, feed that to the java.nio.file API, and then use that to ask if it is a file (it will never be a directory; jars are not directories).
Note that this will not work if the resource you're reading from isn't a jar. Note that the class loading API is abstracted: You could find yourself in the scenario where source files are generated from scratch or loaded out of a DB, with more exotic URLs being produced by the getResource method to boot. Thus, this kind of code simply won't work then. Make sure that's okay first.
Thus:
String urlAsString = MyClassName.class.getResource("MyClassName.class").toString(); // produces a link to yourself.
int start = urlAsString.startsWith("file:jar:") ? 8 : urlAsString.startsWith("file:") ? 4 : 0;
int end = urlAsString.lastIndexOf('!');
String jarFileLoc = urlAsString.substring(start, end);
if you want this to apply to actual directories (class files and such can come from dirs instead of files), you could do:
var map = new HashMap<String, String>();
Path root = Paths.get(jarFileLoc);
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
String content = new String(Files.readAllBytes(file), StandardCharsets.UTF_8);
map.put(root.relativize(file), content);
}
});
for a jar, which is really just a zip, it'll be more like:
var map = new HashMap<String, String>();
Path root = Paths.get(jarFileLoc);
try (var fileIn = Files.newInputStream(root)) {
ZipInputStream zip = new ZipInputStream(fileIn);
for (ZipEntry entry = zip.getNextEntry(); entry != null; entry = zip.getNextEntry()) {
String content = new String(zip.readAllBytes(), StandardCharsets.UTF_8);
map.put(entry.getName(), content);
}
}
Make sure you know what charsets are and that UTF_8 is correct here.
Given a java.nio.file.Path to the jar you want to search (jarPath), and a String for the absolute directory name within the jar (directory), this may work for you:
Map<String, String> map = new HashMap<>();
try (FileSystem fs = FileSystems.newFileSystem(jarPath, null)) {
Path dir = fs.getPath(directory);
if (Files.exists(dir)) {
Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
#Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
map.put(file.toString(), Files.readString(file));
return super.visitFile(file, attrs);
}
});
}
}
Files.readString is available with Java 11+. For earlier versions, use:
new String(Files.readAllBytes(file), StandardCharsets.UTF_8)

Read directory inside JAR with InputStreamReader

So, this question has been asked a million times i believed and I've been reading them for a couple of hours and trying several options given by some people but none of them work for me.
I want to list all the files inside a directory inside the application's JAR, so in IDE this works:
File f = new File(this.getClass().getResource("/resources/").getPath());
for(String s : f.list){
System.out.println(s);
}
That gives me all the files inside the directory.
Now, i've tried this also:
InputStream in = this.getClass().getClassLoader().getResourceAsStream("resources/");
InputStreamReader inReader = new InputStreamReader(in);
Scanner scan = new Scanner(inReader);
while (scan.hasNext()) {
String s = scan.next();
System.out.println("read: " + s);
}
System.out.println("END OF LINE");
And from IDE it prints ALL the files in the directory. Outside IDE prints: "END OF LINE".
Now, I can find an entry inside a Jar with this too:
String s = new File(this.getClass().getResource("").getPath()).getParent().replaceAll("(!|file:\\\\)", "");
JarFile jar = new JarFile(s);
JarEntry entry = jar.getJarEntry("resources");
if (entry != null){
System.out.println("EXISTS");
System.out.println(entry.getSize());
}
That's some horrible coding i had to do to that String.
Anyway... I can't get the list of resources inside the "resources" directory within the Jar... How can I do this???
There's no way to simply get a filtered list of internal resources without first enumerating over the contents of the Jar file.
Luckily, that's actually not that hard (and luckily for me you've done most of the hardwork).
Basically, once you have a reference to the JarFile, you simple need to ask for its' entries and iterate over that list.
By checking the JarEntry name for the required match (ie resources), you can filter the elements you want...
For example...
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class ReadMyResources {
public static void main(String[] args) {
new ReadMyResources();
}
public ReadMyResources() {
JarFile jf = null;
try {
String s = new File(this.getClass().getResource("").getPath()).getParent().replaceAll("(!|file:\\\\)", "");
jf = new JarFile(s);
Enumeration<JarEntry> entries = jf.entries();
while (entries.hasMoreElements()) {
JarEntry je = entries.nextElement();
if (je.getName().startsWith("resources")) {
System.out.println(je.getName());
}
}
} catch (IOException ex) {
ex.printStackTrace();
} finally {
try {
jf.close();
} catch (Exception e) {
}
}
}
}
Caveat
This type of question actually gets ask a bit. Rather then trying to read the contents of the Jar at runtime, it would be better to produce some kind of text file which contained a list of the available resources.
This could be produced by your build process dynamically before the Jar file is created. It would be a much simpler solution to then read this file in (via getClass().getResource(), for example) and then look up each resource list in the text file...IMHO
For Spring Framework users, have a look at PathMatchingResourcePatternResolver to do something like the following:
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath:path/to/resource/*.*");
for (Resource resource : resources) {
InputStream inStream = resource.getInputStream();
// Do something with the input stream
}
My case was to read a directory inside resources:
As my requirement was to transform resource directory to io.File, finally it looked like this:
public static File getResourceDirectory(String resource) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL res = classLoader.getResource(resource);
File fileDirectory;
if ("jar".equals(res.getProtocol())) {
InputStream input = classLoader.getResourceAsStream(resource);
fileDirectory = Files.createTempDir();
List<String> fileNames = IOUtils.readLines(input, StandardCharsets.UTF_8);
fileNames.forEach(name -> {
String fileResourceName = resource + File.separator + name;
File tempFile = new File(fileDirectory.getPath() + File.pathSeparator + name);
InputStream fileInput = classLoader.getResourceAsStream(resourceFileName);
FileUtils.copyInputStreamToFile(fileInput, tempFile);
});
fileDirectory.deleteOnExit();
} else {
fileDirectory = new File(res.getFile());
}
return fileDirectory;
}
If resources are in jar, we copy it to temp directory that will be deleted on application end.
Then calling getResourceDirectory("migrations") returned me io.File directory for further use.

How do you make a function that will create a .jar file in Java?

I've made some code in Java that will change some files in another .jar file, and I know that the unpacking/changing works, but the repacking doesn't. It does succeed, but when I compare the new one and the original (I removed the code that changed the files), they differed. What's interesting is that when I extracted them both into different directories, and I runned diff -rqy on them both, it didn't show any difference.
Here is the current function:
public static void add(File source, JarOutputStream target, String removeme)
throws IOException
{
BufferedInputStream in = null;
try
{
File source2 = new File(source.getPath().replaceAll("^" + removeme,
""));
// File source2 = source;
if (source.isDirectory())
{
String name = source2.getPath().replace("\\", "/");
if (!name.isEmpty())
{
if (!name.endsWith("/"))
name += "/";
JarEntry entry = new JarEntry(name);
entry.setTime(source.lastModified());
target.putNextEntry(entry);
target.closeEntry();
}
for (File nestedFile : source.listFiles())
add(nestedFile, target, removeme);
return;
}
JarEntry entry = new JarEntry(source2.getPath().replace("\\", "/"));
entry.setTime(source.lastModified());
target.putNextEntry(entry);
in = new BufferedInputStream(new FileInputStream(source));
byte[] buffer = new byte[2048];
while (true)
{
int count = in.read(buffer);
if (count == -1)
break;
target.write(buffer, 0, count);
}
target.closeEntry();
}
finally
{
if (in != null)
in.close();
}
}
I call it like this:
JarOutputStream zip = new JarOutputStream(
new FileOutputStream(JARFILE));
for (File nestedFile : new File(DIRECTORY).listFiles())
{
Utils.add(nestedFile, zip,
new File(DIRECTORY).getAbsolutePath());
}
zip.close();
Can anyone direct me on what to change in the function, or what other function I should use? The directory has subdirectories, so I need a function that will scan them.
Thanks in advance!
Edit: I don't want something using the jar command, because I don't want the user to need to install the JDK. I want something using pure Java (libraries are OK, as long as I can include them in the program).
Edit 2: I'm making a Minecraft modder (like MCPatcher and ModLoader), but when I run java -jar minecraft.jar, it gives me this: Invalid or corrupt jarfile. The correct .jar doesn't give this (just a main class error, which is supposed to happen).
I think you maybe interested in java.util.jar. This link maybe useful for you..
http://www.theserverside.com/discussions/thread.tss?thread_id=32600

How to use java.util.zip to archive/deflate string in java for use in Google Earth?

Use Case
I need to package up our kml which is in a String into a kmz response for a network link in Google Earth. I would like to also wrap up icons and such while I'm at it.
Problem
Using the implementation below I receive errors from both WinZip and Google Earth that the archive is corrupted or that the file cannot be opened respectively. The part that deviates from other examples I'd built this from are the lines where the string is added:
ZipEntry kmlZipEntry = new ZipEntry("doc.kml");
out.putNextEntry(kmlZipEntry);
out.write(kml.getBytes("UTF-8"));
Please point me in the right direction to correctly write the string so that it is in doc.xml in the resulting kmz file. I know how to write the string to a temporary file, but I would very much like to keep the operation in memory for understandability and efficiency.
private static final int BUFFER = 2048;
private static void kmz(OutputStream os, String kml)
{
try{
BufferedInputStream origin = null;
ZipOutputStream out = new ZipOutputStream(os);
out.setMethod(ZipOutputStream.DEFLATED);
byte data[] = new byte[BUFFER];
File f = new File("./icons"); //folder containing icons and such
String files[] = f.list();
if(files != null)
{
for (String file: files) {
LOGGER.info("Adding to KMZ: "+ file);
FileInputStream fi = new FileInputStream(file);
origin = new BufferedInputStream(fi, BUFFER);
ZipEntry entry = new ZipEntry(file);
out.putNextEntry(entry);
int count;
while((count = origin.read(data, 0, BUFFER)) != -1) {
out.write(data, 0, count);
}
origin.close();
}
}
ZipEntry kmlZipEntry = new ZipEntry("doc.kml");
out.putNextEntry(kmlZipEntry);
out.write(kml.getBytes("UTF-8"));
}
catch(Exception e)
{
LOGGER.error("Problem creating kmz file", e);
}
}
Bonus points for showing me how to put the supplementary files from the icons folder into a similar folder within the archive as opposed to at the same layer as the doc.kml.
Update Even when saving the string to a temp file the errors occur. Ugh.
Use Case Note The use case is for use in a web app, but the code to get the list of files won't work there. For details see how-to-access-local-files-on-server-in-jboss-application
You forgot to call close() on ZipOutputStream. Best place to call it is the finally block of the try block where it's been created.
Update: To create a folder, just prepend its name in the entry name.
ZipEntry entry = new ZipEntry("icons/" + file);

Loading all files in a directory in a Java applet

How would one go about programatically loading all the resource files in a given directory in a JAR file for an applet? The resources will probably change several times over the lifetime of the program so I don't want to hardcode names in.
Normally I would just traverse the directory structure using File.list(), but I get permission issues when trying to do that within an applet. I also looked at using an enumeration with something line ClassLoader.getResources() but it only finds files of the same name within the JAR file.
Essentially what I want to do is (something like) this:
ClassLoader imagesURL = this.getClass().getClassLoader();
MediaTracker tracker = new MediaTracker(this);
Enumeration<URL> images = imagesURL.getResources("resources/images/image*.gif");
while (images.hasMoreElements()){
tracker.add(getImage(images.nextElement(), i);
i++;
}
I know I'm probably missing some obvious function, but I've spent hours searching through tutorials and documentation for a simple way to do this within an unsigned applet.
You can do it in two ways:
Rename your images, so you can enumerate them via some algorithm (e.g. image_1, image_2, image_3), then you can collect all resources you need in one go.
Otherwise you need to code a lot. The idea is that you have to:
determine the path to your JAR file:
private static final String ANCHOR_NAME = "some resource you know";
URL location = getClass().getClassLoader().getResource(ANCHOR_NAME);
URL jarLocation;
String protocol = location.getProtocol();
if (protocol.equalsIgnoreCase("jar"))
{
String path = location.getPath();
int index = path.lastIndexOf("!/" + ANCHOR_NAME);
if(index != -1)
jarLocation = new URL(path.substring(0, index));
}
if (protocol.equalsIgnoreCase("file"))
{
String string = location.toString();
int index = string.lastIndexOf(ANCHOR_NAME);
if(index != -1)
jarLocation = new URL(string.substring(0, index));
}
open it as java.util.jar.JarFile
JarFile jarFile = new JarFile(jarLocation);
iterate through all entries and match their name with a given mask
for (Enumeration entries = jarFile.entries(); entries.hasMoreElements();)
{
JarEntry entry = (JarEntry) entries.nextElement();
String entryPath = entry.getName();
if (entryPath.endsWith(".jpg"))
{
// do something with it
}
}
For additional code support, I will refer you to Spring PathMatchingResourcePatternResolver#doFindPathMatchingJarResources().

Categories