Copy directory from a jar file
I have recently developed an application and created the jar file.
One of my classes creates an output directory, populating it with files from its resource.
My code is something like this:
// Copy files from dir "template" in this class resource to output.
private void createOutput(File output) throws IOException {
File template = new File(FileHelper.URL2Path(getClass().getResource("template")));
FileHelper.copyDirectory(template, output);
}
Unfortunately this doesn't work.
I tried the following without luck:
Using Streams to solve similar stuff on other classes but it doesn't work with dirs. Code was similar to http://www.exampledepot.com/egs/java.io/CopyFile.html
Creating the File template with
new File(getClass().getResource("template").toUri())
While writing this I was thinking about instead of having a template dir in the resource path having a zip file of it. Doing it this way I could get the file as an inputStream and unzip it where I need to. But I am not sur开发者_如何学运维e if it's the correct way.
Thanks for the solution! For others, the following doesn't make use of the auxiliary classes (except for StringUtils)
/I added extra information for this solution, check the end of the code, Zegor V/
public class FileUtils {
public static boolean copyFile(final File toCopy, final File destFile) {
try {
return FileUtils.copyStream(new FileInputStream(toCopy),
new FileOutputStream(destFile));
} catch (final FileNotFoundException e) {
e.printStackTrace();
}
return false;
}
private static boolean copyFilesRecusively(final File toCopy,
final File destDir) {
assert destDir.isDirectory();
if (!toCopy.isDirectory()) {
return FileUtils.copyFile(toCopy, new File(destDir, toCopy.getName()));
} else {
final File newDestDir = new File(destDir, toCopy.getName());
if (!newDestDir.exists() && !newDestDir.mkdir()) {
return false;
}
for (final File child : toCopy.listFiles()) {
if (!FileUtils.copyFilesRecusively(child, newDestDir)) {
return false;
}
}
}
return true;
}
public static boolean copyJarResourcesRecursively(final File destDir,
final JarURLConnection jarConnection) throws IOException {
final JarFile jarFile = jarConnection.getJarFile();
for (final Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements();) {
final JarEntry entry = e.nextElement();
if (entry.getName().startsWith(jarConnection.getEntryName())) {
final String filename = StringUtils.removeStart(entry.getName(), //
jarConnection.getEntryName());
final File f = new File(destDir, filename);
if (!entry.isDirectory()) {
final InputStream entryInputStream = jarFile.getInputStream(entry);
if(!FileUtils.copyStream(entryInputStream, f)){
return false;
}
entryInputStream.close();
} else {
if (!FileUtils.ensureDirectoryExists(f)) {
throw new IOException("Could not create directory: "
+ f.getAbsolutePath());
}
}
}
}
return true;
}
public static boolean copyResourcesRecursively( //
final URL originUrl, final File destination) {
try {
final URLConnection urlConnection = originUrl.openConnection();
if (urlConnection instanceof JarURLConnection) {
return FileUtils.copyJarResourcesRecursively(destination,
(JarURLConnection) urlConnection);
} else {
return FileUtils.copyFilesRecusively(new File(originUrl.getPath()),
destination);
}
} catch (final IOException e) {
e.printStackTrace();
}
return false;
}
private static boolean copyStream(final InputStream is, final File f) {
try {
return FileUtils.copyStream(is, new FileOutputStream(f));
} catch (final FileNotFoundException e) {
e.printStackTrace();
}
return false;
}
private static boolean copyStream(final InputStream is, final OutputStream os) {
try {
final byte[] buf = new byte[1024];
int len = 0;
while ((len = is.read(buf)) > 0) {
os.write(buf, 0, len);
}
is.close();
os.close();
return true;
} catch (final IOException e) {
e.printStackTrace();
}
return false;
}
private static boolean ensureDirectoryExists(final File f) {
return f.exists() || f.mkdir();
}
}
It uses only one external library from the Apache Software Foundation, however the used functions are only :
public static String removeStart(String str, String remove) {
if (isEmpty(str) || isEmpty(remove)) {
return str;
}
if (str.startsWith(remove)){
return str.substring(remove.length());
}
return str;
}
public static boolean isEmpty(CharSequence cs) {
return cs == null || cs.length() == 0;
}
My knowledge is limited on Apache licence, but you can use this methods in your code without library. However, i am not responsible for licence issues, if there is.
Using Java7+ this can be achieved by creating FileSystem
and then using walkFileTree
to copy files recursively.
public void copyFromJar(String source, final Path target) throws URISyntaxException, IOException {
URI resource = getClass().getResource("").toURI();
FileSystem fileSystem = FileSystems.newFileSystem(
resource,
Collections.<String, String>emptyMap()
);
final Path jarPath = fileSystem.getPath(source);
Files.walkFileTree(jarPath, new SimpleFileVisitor<Path>() {
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;
}
});
}
The method can be used like this:
copyFromJar("/path/to/the/template/in/jar", Paths.get("/tmp/from-jar"))
I think your approach of using a zip file makes sense. Presumably you'll do a getResourceAsStream
to get at the internals of the zip, which will logically look like a directory tree.
A skeleton approach:
InputStream is = getClass().getResourceAsStream("my_embedded_file.zip");
ZipInputStream zis = new ZipInputStream(is);
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
// do something with the entry - for example, extract the data
}
I hated the idea of using the ZIP file method posted earlier, so I came up with the following.
public void copyResourcesRecursively(URL originUrl, File destination) throws Exception {
URLConnection urlConnection = originUrl.openConnection();
if (urlConnection instanceof JarURLConnection) {
copyJarResourcesRecursively(destination, (JarURLConnection) urlConnection);
} else if (urlConnection instanceof FileURLConnection) {
FileUtils.copyFilesRecursively(new File(originUrl.getPath()), destination);
} else {
throw new Exception("URLConnection[" + urlConnection.getClass().getSimpleName() +
"] is not a recognized/implemented connection type.");
}
}
public void copyJarResourcesRecursively(File destination, JarURLConnection jarConnection ) throws IOException {
JarFile jarFile = jarConnection.getJarFile();
for (JarEntry entry : CollectionUtils.iterable(jarFile.entries())) {
if (entry.getName().startsWith(jarConnection.getEntryName())) {
String fileName = StringUtils.removeStart(entry.getName(), jarConnection.getEntryName());
if (!entry.isDirectory()) {
InputStream entryInputStream = null;
try {
entryInputStream = jarFile.getInputStream(entry);
FileUtils.copyStream(entryInputStream, new File(destination, fileName));
} finally {
FileUtils.safeClose(entryInputStream);
}
} else {
FileUtils.ensureDirectoryExists(new File(destination, fileName));
}
}
}
}
Example Useage (copies all files from the classpath resource "config" to "${homeDirectory}/config":
File configHome = new File(homeDirectory, "config/");
//noinspection ResultOfMethodCallIgnored
configHome.mkdirs();
copyResourcesRecursively(super.getClass().getResource("/config"), configHome);
This should work both for copying from both flat files as well as Jar files.
Note: The code above uses some custom utility classes (FileUtils, CollectionUtils) as well as some from Apache commons-lang (StringUtils), but the functions should be named fairly obviously.
The answer of lpiepiora, is correct! But there is a minor issue, The source, should be a jar Url. When the source path is path to a file system, then the above code will not work proper. To solve this problem, you should use the ReferencePath, the code, you can get from the following link: Read from file system via FileSystem object The new code of copyFromJar should like:
public class ResourcesUtils {
public static void copyFromJar(final String sourcePath, final Path target) throws URISyntaxException,
IOException {
final PathReference pathReference = PathReference.getPath(new URI(sourcePath));
final Path jarPath = pathReference.getPath();
Files.walkFileTree(jarPath, new SimpleFileVisitor<Path>() {
private Path currentTarget;
@Override
public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException {
currentTarget = target.resolve(jarPath.relativize(dir)
.toString());
Files.createDirectories(currentTarget);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
Files.copy(file, target.resolve(jarPath.relativize(file)
.toString()), StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
});
}
public static void main(final String[] args) throws MalformedURLException, URISyntaxException, IOException {
final String sourcePath = "jar:file:/c:/temp/example.jar!/src/main/resources";
ResourcesUtils.copyFromJar(sourcePath, Paths.get("c:/temp/resources"));
}
I'm not sure what FileHelper
is or does, but you will NOT be able to copy files (or directories) directly from JAR. Using InputStream as you've mentioned is the correct way (from either jar or zip):
InputStream is = getClass().getResourceAsStream("file_in_jar");
OutputStream os = new FileOutputStream("dest_file");
byte[] buffer = new byte[4096];
int length;
while ((length = is.read(buffer)) > 0) {
os.write(buffer, 0, length);
}
os.close();
is.close();
You'll need to do the above (handling exceptions appropriately, of course) for each of your files. You may or may not be able (depending on your deployment configuration) to read jar file in question as JarFile (it may not be available as an actual file if deployed as part of non-expanded web app, for example). If you can read it, you should be able to iterate through list of JarEntry instances and thus reconstitute your directory structure; otherwise you may need to store it elsewhere (within text or xml resource, for example)
You may want to take a look at Commons IO library - it provides a lot of commonly used stream / file functionality including copying.
Here is a working version from the tess4j project:
/**
* This method will copy resources from the jar file of the current thread and extract it to the destination folder.
*
* @param jarConnection
* @param destDir
* @throws IOException
*/
public void copyJarResourceToFolder(JarURLConnection jarConnection, File destDir) {
try {
JarFile jarFile = jarConnection.getJarFile();
/**
* Iterate all entries in the jar file.
*/
for (Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements();) {
JarEntry jarEntry = e.nextElement();
String jarEntryName = jarEntry.getName();
String jarConnectionEntryName = jarConnection.getEntryName();
/**
* Extract files only if they match the path.
*/
if (jarEntryName.startsWith(jarConnectionEntryName)) {
String filename = jarEntryName.startsWith(jarConnectionEntryName) ? jarEntryName.substring(jarConnectionEntryName.length()) : jarEntryName;
File currentFile = new File(destDir, filename);
if (jarEntry.isDirectory()) {
currentFile.mkdirs();
} else {
InputStream is = jarFile.getInputStream(jarEntry);
OutputStream out = FileUtils.openOutputStream(currentFile);
IOUtils.copy(is, out);
is.close();
out.close();
}
}
}
} catch (IOException e) {
// TODO add logger
e.printStackTrace();
}
}
I know this question is kind of old now but after trying some answers that didn't work and others that required a whole library for just one method, I decided to put together a class. It doesn't require third-party libraries and it's been tested with Java 8. There are four public methods: copyResourcesToTempDir
, copyResourcesToDir
, copyResourceDirectory
and jar
.
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.Files;
import java.util.Enumeration;
import java.util.Optional;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* A helper to copy resources from a JAR file into a directory.
*/
public final class ResourceCopy {
/**
* URI prefix for JAR files.
*/
private static final String JAR_URI_PREFIX = "jar:file:";
/**
* The default buffer size.
*/
private static final int BUFFER_SIZE = 8 * 1024;
/**
* Copies a set of resources into a temporal directory, optionally preserving
* the paths of the resources.
* @param preserve Whether the files should be placed directly in the
* directory or the source path should be kept
* @param paths The paths to the resources
* @return The temporal directory
* @throws IOException If there is an I/O error
*/
public File copyResourcesToTempDir(final boolean preserve,
final String... paths)
throws IOException {
final File parent = new File(System.getProperty("java.io.tmpdir"));
File directory;
do {
directory = new File(parent, String.valueOf(System.nanoTime()));
} while (!directory.mkdir());
return this.copyResourcesToDir(directory, preserve, paths);
}
/**
* Copies a set of resources into a directory, preserving the paths
* and names of the resources.
* @param directory The target directory
* @param preserve Whether the files should be placed directly in the
* directory or the source path should be kept
* @param paths The paths to the resources
* @return The temporal directory
* @throws IOException If there is an I/O error
*/
public File copyResourcesToDir(final File directory, final boolean preserve,
final String... paths) throws IOException {
for (final String path : paths) {
final File target;
if (preserve) {
target = new File(directory, path);
target.getParentFile().mkdirs();
} else {
target = new File(directory, new File(path).getName());
}
this.writeToFile(
Thread.currentThread()
.getContextClassLoader()
.getResourceAsStream(path),
target
);
}
return directory;
}
/**
* Copies a resource directory from inside a JAR file to a target directory.
* @param source The JAR file
* @param path The path to the directory inside the JAR file
* @param target The target directory
* @throws IOException If there is an I/O error
*/
public void copyResourceDirectory(final JarFile source, final String path,
final File target) throws IOException {
final Enumeration<JarEntry> entries = source.entries();
final String newpath = String.format("%s/", path);
while (entries.hasMoreElements()) {
final JarEntry entry = entries.nextElement();
if (entry.getName().startsWith(newpath) && !entry.isDirectory()) {
final File dest =
new File(target, entry.getName().substring(newpath.length()));
final File parent = dest.getParentFile();
if (parent != null) {
parent.mkdirs();
}
this.writeToFile(source.getInputStream(entry), dest);
}
}
}
/**
* The JAR file containing the given class.
* @param clazz The class
* @return The JAR file or null
* @throws IOException If there is an I/O error
*/
public Optional<JarFile> jar(final Class<?> clazz) throws IOException {
final String path =
String.format("/%s.class", clazz.getName().replace('.', '/'));
final URL url = clazz.getResource(path);
Optional<JarFile> optional = Optional.empty();
if (url != null) {
final String jar = url.toString();
final int bang = jar.indexOf('!');
if (jar.startsWith(ResourceCopy.JAR_URI_PREFIX) && bang != -1) {
optional = Optional.of(
new JarFile(
jar.substring(ResourceCopy.JAR_URI_PREFIX.length(), bang)
)
);
}
}
return optional;
}
/**
* Writes an input stream to a file.
* @param input The input stream
* @param target The target file
* @throws IOException If there is an I/O error
*/
private void writeToFile(final InputStream input, final File target)
throws IOException {
final OutputStream output = Files.newOutputStream(target.toPath());
final byte[] buffer = new byte[ResourceCopy.BUFFER_SIZE];
int length = input.read(buffer);
while (length > 0) {
output.write(buffer, 0, length);
length = input.read(buffer);
}
input.close();
output.close();
}
}
You could use the ClassLoader to obtain a stream to the resource. Once you have obtained an InputStream, you can read off, and write the contents of the stream, onto an OutputStream.
In your case, you'll need to create several OutputStream instances, one for each file that you want to copy over to the destination. This of course, requires that you know of the file names before hand.
For this task, it is preferred to use getResourceAsStream, rather than getResource or getResources().
I have faced the similair problem recently. I tried to extract folder from java resources. So I resolved this issue with Spring PathMatchingResourcePatternResolver.
This code gets all files and directories from the specified resource:
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ resourceFolder + "/**");
And this is the class that copy all files and directories from the resource to the disk path.
public class ResourceExtractor {
public static final Logger logger =
Logger.getLogger(ResourceExtractor.class);
public void extract(String resourceFolder, String destinationFolder){
try {
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ resourceFolder + "/**");
URI inJarUri = new DefaultResourceLoader().getResource("classpath:" + resourceFolder).getURI();
for (Resource resource : resources){
String relativePath = resource
.getURI()
.getRawSchemeSpecificPart()
.replace(inJarUri.getRawSchemeSpecificPart(), "");
if (relativePath.isEmpty()){
continue;
}
if (relativePath.endsWith("/") || relativePath.endsWith("\\")) {
File dirFile = new File(destinationFolder + relativePath);
if (!dirFile.exists()) {
dirFile.mkdir();
}
}
else{
copyResourceToFilePath(resource, destinationFolder + relativePath);
}
}
}
catch (IOException e){
logger.debug("Extraction failed!", e );
}
}
private void copyResourceToFilePath(Resource resource, String filePath) throws IOException{
InputStream resourceInputStream = resource.getInputStream();
File file = new File(filePath);
if (!file.exists()) {
FileUtils.copyInputStreamToFile(resourceInputStream, file);
}
}
}
You can use my library: compile group: 'com.github.ardenliu', name: 'arden-file', version: '0.0.4'
ResourcesUtils class: copyFromClassPath(final String resourcePath, final Path targetRoot)
Source Code: https://github.com/ardenliu/common/blob/master/arden-file/src/main/java/com/github/ardenliu/common/file/ResourcesUtils.java
Junit testing: One testing case for Eclipse classpath; another one for jar https://github.com/ardenliu/common/blob/master/arden-file/src/test/java/com/github/ardenliu/common/file/ResourcesUtilsTest.java
I liked the response from @nivekastoreth (https://stackoverflow.com/a/2993908/13768174) but was not able to use it directly because it was relying on third party libraries.
I just rewrote it to use java.nio.file.Files
Let's say I want to copy a folder like this:
parent
|_folder
|_item1
|_item2
|_folder2
I can just call the method like this:
final URL configFolderURL = getClass().getResource("in/jar/path/folder");
Path targetDir = Files.createDirectory(Path.of("a","new","dir"));
copyJarResourcesRecursively(targetDir, (JarURLConnection) configFolderURL.openConnection());
Here is the implementation:
private void copyJarResourcesRecursively(Path destination, JarURLConnection jarConnection) throws IOException {
JarFile jarFile = jarConnection.getJarFile();
for (Iterator<JarEntry> it = jarFile.entries().asIterator(); it.hasNext();) {
JarEntry entry = it.next();
if (entry.getName().startsWith(jarConnection.getEntryName())) {
if (!entry.isDirectory()) {
try (InputStream entryInputStream = jarFile.getInputStream(entry)) {
Files.copy(entryInputStream, Paths.get(destination.toString(), entry.getName()));
}
} else {
Files.createDirectories(Paths.get(destination.toString(), entry.getName()));
}
}
}
}
精彩评论