Reading Directory Contents From a JAR file - java

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)

Related

Java 8: How to copy files written in a list to a TXT file from one directory to another directory?

I have a simple text file called small_reports.txt that looks like:
report_2021_05_02.csv
report_2021_05_05.csv
report_2021_06_08.csv
report_2021_06_25.csv
report_2021_07_02.csv
This reported is generated with my java code and takes in each of these files from the directory /work/dir1/reports and writes them into the file combined_reports.txt and then places the txt file back into /work/dir1/reports.
My question is, for each line in small_reports.txt, find that same file (line) in /work/dir1/reports and then COPY them to a new directory called /work/dir1/smallreports?
Using Java 8 & NIO (which is really helpful and good) I have tried:
Path source = Paths.get("/work/dir1/reports/combined_reports.txt");
Path target = Paths.get("/work/dir1/smallreports/", "combined_reports.txt");
if (Files.notExists(target) && target != null) {
Files.createDirectories(Paths.get(target.toString()));
}
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
But this is just copying the actual txt file combined_reports.txt into the new directory and not the contents inside like i thought it would.
final String SOURCE_DIR = "/tmp";
final String TARGET_DIR = "/tmp/root/delme";
List<String> csvFileNames = Files.readAllLines(FileSystems.getDefault().getPath("small_reports.txt"), Charset.forName("UTF-8"));
for (String csvFileName : csvFileNames) {
Path source = Paths.get(SOURCE_DIR, csvFileName);
Path target = Paths.get(TARGET_DIR, csvFileName);
if (Files.notExists(target) && target != null) {
Files.createDirectories(Paths.get(target.toString()));
}
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
}
Should do it for you. Obviously change the constants appropriately

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.

Is it possible to create a NEW zip file using the java FileSystem?

I've successfully modified the contents of a (existing) zip file using the FileSystem provided by java 7, but when I tried to create a NEW zip file by this method it fails, with the error message that says: "zip END header not found", it is logical because of the way I'm doing it, first I create the file (Files.createFile) which is a completely empty file, and then I try to access to its file system , and since the file is empty its impossible to find any header inside the zip, my question is is there any way to create a new zip file completely empty using this method?; the hack that I've considered is adding an empty new ZipEntry to a the zip file and then using that new empty file to crate the file system based on it, but i really want to think that the guys of oracle implemented a better (easier) way to do this with nio and the filesystems...
this is my code (the error appears when creating the file system):
if (!zipLocation.toFile().exists()) {
if (creatingFile) {
Files.createFile(zipLocation);
}else {
return false;
}
} else if (zipLocation.toFile().exists() && !replacing) {
return false;
}
final FileSystem fs = FileSystems.newFileSystem(zipLocation, null);
.
.
.
zipLocation is a Path
creatingFile is a boolean
ANSWER:
in my particular case the answer given didn't work appropriately because of the spaces in the path, therefore i have to do it the way i didn't want to:
Files.createFile(zipLocation);
ZipOutputStream out = new ZipOutputStream(
new FileOutputStream(zipLocation.toFile()));
out.putNextEntry(new ZipEntry(""));
out.closeEntry();
out.close();
it does not mean that the given answer is wrong, it just didn't work for my particular case
As described in The Oracle Site:
public static void createZip(Path zipLocation, Path toBeAdded, String internalPath) throws Throwable {
Map<String, String> env = new HashMap<String, String>();
// check if file exists
env.put("create", String.valueOf(Files.notExists(zipLocation)));
// use a Zip filesystem URI
URI fileUri = zipLocation.toUri(); // here
URI zipUri = new URI("jar:" + fileUri.getScheme(), fileUri.getPath(), null);
System.out.println(zipUri);
// URI uri = URI.create("jar:file:"+zipLocation); // here creates the
// zip
// try with resource
try (FileSystem zipfs = FileSystems.newFileSystem(zipUri, env)) {
// Create internal path in the zipfs
Path internalTargetPath = zipfs.getPath(internalPath);
// Create parent directory
Files.createDirectories(internalTargetPath.getParent());
// copy a file into the zip file
Files.copy(toBeAdded, internalTargetPath, StandardCopyOption.REPLACE_EXISTING);
}
}
public static void main(String[] args) throws Throwable {
Path zipLocation = FileSystems.getDefault().getPath("a.zip").toAbsolutePath();
Path toBeAdded = FileSystems.getDefault().getPath("a.txt").toAbsolutePath();
createZip(zipLocation, toBeAdded, "aa/aa.txt");
}

Read contents of jar file given specific path

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();

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

Categories