Inside my resources folder I have a folder called init. I want to copy that folder and everything inside of it to the outside of the jar in a folder called ready. And I want to do that without using any external libraries, just pure java.
I have tried the following
public static void copyFromJar(String source, final Path target)
throws
URISyntaxException,
IOException
{
URI resource = ServerInitializer.class.getResource("").toURI();
FileSystem fileSystem = FileSystems.newFileSystem(resource, Collections.<String, String>emptyMap());
final Path jarPath = fileSystem.getPath(source);
Files.walkFileTree(jarPath, new SimpleFileVisitor<>()
{
private Path currentTarget;
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws
IOException
{
currentTarget = target.resolve(jarPath.relativize(dir).toString());
Files.createDirectories(currentTarget);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws
IOException
{
Files.copy(file, target.resolve(jarPath.relativize(file).toString()),
StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
});
}
However my application already dies at line
FileSystem fileSystem = FileSystems.newFileSystem(resource, Collections.<String, String>emptyMap());
with exception
java.lang.IllegalArgumentException: Path component should be '/'
when I call
copyFromJar("/init", Paths.get("ready");
Any idea what I am doing wrong? Or can someone provide me code to copy directory from jar to outside of it without using any external libraries?
Just for reference I already looked at this solution but it is too old and uses apache library but I need pure java solution that works both on windows and linux.
That's a crazy complicated way to do it. It's also dependent entirely on your app being in a jar, which makes testing, deployments into runtime-modularized loaders, etc - tricky.
There are much easier ways to do this. Specifically, SomeClass.class.getResourceAsStream("/foo/bar")
will get you an InputStream
for the entry /foo/bar
in the same classpath root as where the class file representing SomeClass
lives - be it a jar file, a plain old directory, or something more exotic.
That's how you should 'copy' your files over. This trivial code:
String theResource = "com/foo/quillionsapp/toCopy/example.txt";
Path targetPath = ....;
try (var in = YourClass.class.getResourceAsStream("/" + theResource)) {
Path tgt = targetPath.resolve(theResource);
Files.createDirectories(tgt.getParent());
try (var out = Files.newOutputStream(tgt)) {
in.transferTo(out);
}
}
Now all you need is a list of all files to be copied. The classpath abstraction simply does not support listing. So, any attempt to hack that in there is just that: A hack. It'll fail when you e.g. have modularized loaders and the like. You can just do that - you're already writing code that asplodes on you if your code is not in a jar file. It's not hard to write a method that gives you a list of all contents for both 'dir on the file system' based classpaths as well as 'jar file' based ones, but there is a ready alternative: Make a text file with a known name that lists all resources at compile time. You can write it yourself, or you can script it. Can be as trivial as ls src/resources/* > filesToCopy.txt
or whatnot. You can also use annotation processors to produce such a file.
Once you know the file exists (it's in the jar same as the files you want to copy), read it with getResourceAsStream
just the same way, and now you have a list of resources to write out using the above code. That trick means your code is entirely compatible with the API of ClassLoader: You are just relying on the guaranteed functionality of 'get me an inputstream with the full data of this named resource' and nothing else.