hell_storm2004
hell_storm2004

Reputation: 1605

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.

Upvotes: 2

Views: 1291

Answers (2)

boot-and-bonnet
boot-and-bonnet

Reputation: 761

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)

Upvotes: 1

rzwitserloot
rzwitserloot

Reputation: 102785

  1. getClass() is the wrong approach for jobs like this; it breaks if anybody subclasses. The proper way is to use MyClassName.class instead.

  2. 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).

  3. 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).

  4. 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.

Upvotes: 2

Related Questions