How to get a resource(folder) from a jar? - java

Basically what i'm trying to do is parse through all the files of a directory and add all the childen to a List< File> .
The catch is that I don't have a main function, I'm running just a jar. So I can't do this:
URL url = MyClass.class.getResource("resources/");
if (url == null) {
// error - missing folder
} else {
File dir = new File(url.toURI()); //throws error when running the jar
for (File nextFile : dir.listFiles()) {
// Do something with nextFile
}
}
as it throws IllegalArgument Exception. I've also tried getting an InputStream with:
InputStream inStream = CompilerUtils.class.getClassLoader().getResourceAsStream(filePath)
But it seems it's not working for files. I'm looking for an alternative of this that works for directories.

Get string represenataion of url url.getFile() it will look like file:/C:/xxx/yyy/x.jar!/resources extract path from it: C:/xxx/yyy/x.jar. Use JarFile class to read folders

Related

Java loading files from the jar

I don't get how to load files from the produced Jar.
This is my code and it works fine inside the IDE, but not when I run the Jar:
URL url = ClassLoader.getSystemResource(".");
try
{
File dir = new File(url.toURI());
for (File f : dir.listFiles())
{
String fn = f.getName();
if (fn.endsWith(".png"))
{
ImageView iv = new ImageView(fn);
// ...
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
The structure of the Jar is:
META-INF
de (and the rest of the packages with the class files)
file1
file2
... and so on
So the files are directly in the jar not in any subfolder.
Your code doesn't work because File objects cannot be used to access files inside a jar. What you can do is use ZipInputStreams to open & read your jar file alongside ZipEntry's to read the individual files in your jar.
This code will work in a jar, but most likely not in an IDE. In which case, you can detect the current state (IDE or Jar) and execute the desired loading code accordingly.
CodeSource src = ClientMain.class.getProtectionDomain().getCodeSource();
URL jar = src.getLocation();
ZipInputStream zip = new ZipInputStream(jar.openStream());
ZipEntry entry = null;
while ((entry = zip.getNextEntry()) != null) {
String entryName = entry.getName();
if (entryName.endsWith(".png")) {
BufferedImage image = ImageIO.read(zip);
// ...
}
}
Using the URL's already setup, we can determine if the program is in a jar or not with this simple code:
new File(jar.getFile()).toString().endsWith("jar")
This works because when in an IDE, (eclipse in my case)
new File(jar.getFile()).toString() returns
"D:\Java\Current%20Projects\Test\bin"
where as in a jar, I got
"D:\Windows%20Folders\Desktop\Test.jar"

Get folder from Resources folder JAVA

Hi everyone I can't figure out with this problem : this line of code should work
File[] file = (new File(getClass().getResource("resources/images_resultats"))).listFiles();
I want a list of File, these Files are under "images_resultats" under "resources".
It won't work if resources/images_resultats is not in your classpath and/or if it is in a jar file.
Your code is not even correct it should something like:
File[] file = (new File(getClass().getResource("/my/path").toURI())).listFiles();
You can determine what files are in a folder in resources (even if its in a jar) using the FileSystem class.
public static void doSomethingWithResourcesFolder(String inResourcesPath) throws URISyntaxException {
URI uri = ResourcesFolderUts.class.getResource(inResourcesPath).toURI();
try( FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap() ) ){
Path folderRootPath = fileSystem.getPath(inResourcesPath);
Stream<Path> walk = Files.walk(folderRootPath, 1);
walk.forEach(childFileOrFolder -> {
//do something with the childFileOrFolder
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
inResourcesPath should be something like "/images_resultats"
Note that the childFileOrFolder paths can only be used while the FileSystem remains open, if you try to (for example) return the paths then use them later you've get a file system closed exception.
Change ResourcesFolderUts for one of your own classes
Assuming that resources folder is in classpath, this might work.
String folder = getClass().getResource("images_resultats").getFile();
File[] test = new File(folder).listFiles();

Add file to a folder that is inside a zip file java

I am trying to add a txt file into a folder which is inside a zip file.
First, I was extracting all the contents of zip file then adding the txt file and then zipping back.
Then I read about the nio method which I can modify the zip without extracting it. Using this method I can add the txt file to the main folder of zip but I can't go deeper.
testing.zip file has res folder in it.
Here is my code:
Path txtFilePath = Paths.get("\\test\\prefs.txt");
Path zipFilePath = Paths.get("\\test\\testing.zip");
FileSystem fs;
try {
fs = FileSystems.newFileSystem(zipFilePath, null);
Path fileInsideZipPath = fs.getPath("res/prefs.txt"); //when I remover "res/" code works.
Files.copy(txtFilePath, fileInsideZipPath);
fs.close();
} catch (IOException e) {
e.printStackTrace();
}
I get the following exception:
java.nio.file.NoSuchFileException: res/
(edit to give the actual answer)
Do:
fs.getPath("res").resolve("prefs.txt")
instead of:
fs.getPath("res/prefs.txt")
The .resolve() method will do the correct thing with regards to file separators etc.
The fs.getPath("res/prefs.txt") should certainly work and you don't need to split it to fs.getPath("res").resolve("prefs.txt") as the approved answer says.
The exception java.nio.file.NoSuchFileException: res/ is slightly confusing because it mentions file but in fact directory is missing.
I had a similar problem and all I had to do was:
if (fileInsideZipPath.getParent() != null)
Files.createDirectories(fileInsideZipPath.getParent());
See full example:
#Test
public void testAddFileToArchive() throws Exception {
Path fileToAdd1 = rootTestFolder.resolve("notes1.txt");
addFileToArchive(archiveFile, "notes1.txt", fileToAdd1);
Path fileToAdd2 = rootTestFolder.resolve("notes2.txt");
addFileToArchive(archiveFile, "foo/bar/notes2.txt", fileToAdd2);
. . .
}
public void addFileToArchive(Path archiveFile, String pathInArchive, Path srcFile) throws Exception {
FileSystem fs = FileSystems.newFileSystem(archiveFile, null);
Path fileInsideZipPath = fs.getPath(pathInArchive);
if (fileInsideZipPath.getParent() != null) Files.createDirectories(fileInsideZipPath.getParent());
Files.copy(srcFile, fileInsideZipPath, StandardCopyOption.REPLACE_EXISTING);
fs.close();
}
If I remove Files.createDirectories() bit, and ensure clear start with clear test directory, I get:
java.nio.file.NoSuchFileException: foo/bar/
at com.sun.nio.zipfs.ZipFileSystem.checkParents(ZipFileSystem.java:863)
at com.sun.nio.zipfs.ZipFileSystem.newOutputStream(ZipFileSystem.java:528)
at com.sun.nio.zipfs.ZipPath.newOutputStream(ZipPath.java:792)
at com.sun.nio.zipfs.ZipFileSystemProvider.newOutputStream(ZipFileSystemProvider.java:285)
at java.nio.file.Files.newOutputStream(Files.java:216)
at java.nio.file.Files.copy(Files.java:3016)
at java.nio.file.CopyMoveHelper.copyToForeignTarget(CopyMoveHelper.java:126)
at java.nio.file.Files.copy(Files.java:1277)
at my.home.test.zipfs.TestBasicOperations.addFileToArchive(TestBasicOperations.java:111)
at my.home.test.zipfs.TestBasicOperations.testAddFileToArchive(TestBasicOperations.java:51)

Can I delete a JAR after I accesed its internal file through URL?

In Groovy, I am reading a file from inside a JAR, and after some processing, I wish to delete this JAR, but once accessed through the URL, it doesn't seem to let me.
Example:
File jarFile = new File('jarFile.jar')
URL url = jarFile.toURI().toURL()
URL intUrl = new URL("jar:$url!/internalFile.json")
println intUrl.text // reads text correctly
jarFile.delete() // returns false, cannot delete
The Javadoc of the getText() command says the connection is closed at the end of the call, and this JAR normally isn't on classpath. Is there any way to make this code work?
Try setting the sun.zip.disableMemoryMapping system property:
java -Dsun.zip.disableMemoryMapping=true ....
(or however you set system properties when invoking Gradle). ZipFile (which backs jar: URLs) uses memory mapping by default, and this may be causing Windows to think that the file in question is still open. If this is not an option then you could try using the commons-compress ZipFile implementation instead of the java.util.zip one:
#Grab(group='org.apache.commons', module='commons-compress', version='1.4.1')
import org.apache.commons.compress.archivers.zip.*
File jarFile = new File('jarFile.jar')
ZipFile f = new ZipFile(jarFile)
ZipArchiveEntry json = f.getEntry('internalFile.json')
if(json) {
f.getInputStream(json)?.withStream {
println it.getText('UTF-8')
}
}
f.close()
jarFile.delete()
You could try this:
import java.util.zip.ZipFile
File jarFile = new File( 'jarFile.jar' )
String text = new ZipFile( jarFile ).with { zf ->
String result = zf.entries().findResult { ze ->
if( ze.name == 'internalFile.json' ) {
zf.getInputStream( ze ).withReader {
it.text
}
}
}
zf.close()
result
}
println text
jarFile.delete()
To avoid (what I suspect is) the classloader locking the jar file

How can I access a folder inside of a resource folder from inside my jar File?

I have a resources folder/package in the root of my project, I "don't" want to load a certain File. If I wanted to load a certain File, I would use class.getResourceAsStream and I would be fine!! What I actually want to do is to load a "Folder" within the resources folder, loop on the Files inside that Folder and get a Stream to each file and read in the content... Assume that the File names are not determined before runtime... What should I do? Is there a way to get a list of the files inside a Folder in your jar File?
Notice that the Jar file with the resources is the same jar file from which the code is being run...
Finally, I found the solution:
final String path = "sample/folder";
final File jarFile = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath());
if(jarFile.isFile()) { // Run with JAR file
final JarFile jar = new JarFile(jarFile);
final Enumeration<JarEntry> entries = jar.entries(); //gives ALL entries in jar
while(entries.hasMoreElements()) {
final String name = entries.nextElement().getName();
if (name.startsWith(path + "/")) { //filter according to the path
System.out.println(name);
}
}
jar.close();
} else { // Run with IDE
final URL url = Launcher.class.getResource("/" + path);
if (url != null) {
try {
final File apps = new File(url.toURI());
for (File app : apps.listFiles()) {
System.out.println(app);
}
} catch (URISyntaxException ex) {
// never happens
}
}
}
The second block just work when you run the application on IDE (not with jar file), You can remove it if you don't like that.
Try the following.
Make the resource path "<PathRelativeToThisClassFile>/<ResourceDirectory>" E.g. if your class path is com.abc.package.MyClass and your resoure files are within src/com/abc/package/resources/:
URL url = MyClass.class.getResource("resources/");
if (url == null) {
// error - missing folder
} else {
File dir = new File(url.toURI());
for (File nextFile : dir.listFiles()) {
// Do something with nextFile
}
}
You can also use
URL url = MyClass.class.getResource("/com/abc/package/resources/");
The following code returns the wanted "folder" as Path regardless of if it is inside a jar or not.
private Path getFolderPath() throws URISyntaxException, IOException {
URI uri = getClass().getClassLoader().getResource("folder").toURI();
if ("jar".equals(uri.getScheme())) {
FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap(), null);
return fileSystem.getPath("path/to/folder/inside/jar");
} else {
return Paths.get(uri);
}
}
Requires java 7+.
I know this is many years ago . But just for other people come across this topic.
What you could do is to use getResourceAsStream() method with the directory path, and the input Stream will have all the files name from that dir. After that you can concat the dir path with each file name and call getResourceAsStream for each file in a loop.
I had the same problem at hands while i was attempting to load some hadoop configurations from resources packed in the jar... on both the IDE and on jar (release version).
I found java.nio.file.DirectoryStream to work the best to iterate over directory contents over both local filesystem and jar.
String fooFolder = "/foo/folder";
....
ClassLoader classLoader = foofClass.class.getClassLoader();
try {
uri = classLoader.getResource(fooFolder).toURI();
} catch (URISyntaxException e) {
throw new FooException(e.getMessage());
} catch (NullPointerException e){
throw new FooException(e.getMessage());
}
if(uri == null){
throw new FooException("something is wrong directory or files missing");
}
/** i want to know if i am inside the jar or working on the IDE*/
if(uri.getScheme().contains("jar")){
/** jar case */
try{
URL jar = FooClass.class.getProtectionDomain().getCodeSource().getLocation();
//jar.toString() begins with file:
//i want to trim it out...
Path jarFile = Paths.get(jar.toString().substring("file:".length()));
FileSystem fs = FileSystems.newFileSystem(jarFile, null);
DirectoryStream<Path> directoryStream = Files.newDirectoryStream(fs.getPath(fooFolder));
for(Path p: directoryStream){
InputStream is = FooClass.class.getResourceAsStream(p.toString()) ;
performFooOverInputStream(is);
/** your logic here **/
}
}catch(IOException e) {
throw new FooException(e.getMessage());
}
}
else{
/** IDE case */
Path path = Paths.get(uri);
try {
DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path);
for(Path p : directoryStream){
InputStream is = new FileInputStream(p.toFile());
performFooOverInputStream(is);
}
} catch (IOException _e) {
throw new FooException(_e.getMessage());
}
}
Another solution, you can do it using ResourceLoader like this:
import org.springframework.core.io.Resource;
import org.apache.commons.io.FileUtils;
#Autowire
private ResourceLoader resourceLoader;
...
Resource resource = resourceLoader.getResource("classpath:/path/to/you/dir");
File file = resource.getFile();
Iterator<File> fi = FileUtils.iterateFiles(file, null, true);
while(fi.hasNext()) {
load(fi.next())
}
If you are using Spring you can use org.springframework.core.io.support.PathMatchingResourcePatternResolver and deal with Resource objects rather than files. This works when running inside and outside of a Jar file.
PathMatchingResourcePatternResolver r = new PathMatchingResourcePatternResolver();
Resource[] resources = r.getResources("/myfolder/*");
Then you can access the data using getInputStream and the filename from getFilename.
Note that it will still fail if you try to use the getFile while running from a Jar.
As the other answers point out, once the resources are inside a jar file, things get really ugly. In our case, this solution:
https://stackoverflow.com/a/13227570/516188
works very well in the tests (since when the tests are run the code is not packed in a jar file), but doesn't work when the app actually runs normally. So what I've done is... I hardcode the list of the files in the app, but I have a test which reads the actual list from disk (can do it since that works in tests) and fails if the actual list doesn't match with the list the app returns.
That way I have simple code in my app (no tricks), and I'm sure I didn't forget to add a new entry in the list thanks to the test.
Below code gets .yaml files from a custom resource directory.
ClassLoader classLoader = this.getClass().getClassLoader();
URI uri = classLoader.getResource(directoryPath).toURI();
if("jar".equalsIgnoreCase(uri.getScheme())){
Pattern pattern = Pattern.compile("^.+" +"/classes/" + directoryPath + "/.+.yaml$");
log.debug("pattern {} ", pattern.pattern());
ApplicationHome home = new ApplicationHome(SomeApplication.class);
JarFile file = new JarFile(home.getSource());
Enumeration<JarEntry> jarEntries = file.entries() ;
while(jarEntries.hasMoreElements()){
JarEntry entry = jarEntries.nextElement();
Matcher matcher = pattern.matcher(entry.getName());
if(matcher.find()){
InputStream in =
file.getInputStream(entry);
//work on the stream
}
}
}else{
//When Spring boot application executed through Non-Jar strategy like through IDE or as a War.
String path = uri.getPath();
File[] files = new File(path).listFiles();
for(File file: files){
if(file != null){
try {
InputStream is = new FileInputStream(file);
//work on stream
} catch (Exception e) {
log.error("Exception while parsing file yaml file {} : {} " , file.getAbsolutePath(), e.getMessage());
}
}else{
log.warn("File Object is null while parsing yaml file");
}
}
}
Took me 2-3 days to get this working, in order to have the same url that work for both Jar or in local, the url (or path) needs to be a relative path from the repository root.
..meaning, the location of your file or folder from your src folder.
could be "/main/resources/your-folder/" or "/client/notes/somefile.md"
Whatever it is, in order for your JAR file to find it, the url must be a relative path from the repository root.
it must be "src/main/resources/your-folder/" or "src/client/notes/somefile.md"
Now you get the drill, and luckily for Intellij Idea users, you can get the correct path with a right-click on the folder or file -> copy Path/Reference.. -> Path From Repository Root (this is it)
Last, paste it and do your thing.
Simple ... use OSGi. In OSGi you can iterate over your Bundle's entries with findEntries and findPaths.
Inside my jar file I had a folder called Upload, this folder had three other text files inside it and I needed to have an exactly the same folder and files outside of the jar file, I used the code below:
URL inputUrl = getClass().getResource("/upload/blabla1.txt");
File dest1 = new File("upload/blabla1.txt");
FileUtils.copyURLToFile(inputUrl, dest1);
URL inputUrl2 = getClass().getResource("/upload/blabla2.txt");
File dest2 = new File("upload/blabla2.txt");
FileUtils.copyURLToFile(inputUrl2, dest2);
URL inputUrl3 = getClass().getResource("/upload/blabla3.txt");
File dest3 = new File("upload/Bblabla3.txt");
FileUtils.copyURLToFile(inputUrl3, dest3);

Categories

Resources