ResourceUtils.java
package io.extact.rms.platform.util;
import static java.util.function.Predicate.*;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
/**
* Class resource utility.
*/
public class ResourceUtils {
/**
* Get the class resource under the path specified by the argument by URL.
* Search also inside the jar file.Also, if a file is specified in the argument path,
* the URL of the corresponding file is returned.
* The following usage example
* <pre>
* classpath-resources =>
* jar:file:/C:/foo/bar/baz.jar!/META-INF/MANIFEST.MF
* jar:file:/C:/foo/bar/baz.jar!/mpconfig/a.properties
* jar:file:/C:/foo/bar/baz.jar!/mpconfig/b.properties
* file:/C:/for/bar/target/classes/message.properties
* file:/C:/for/bar/target/classes/mpconfig/c.text
* targetDir => mpconfig
* return url =>
* jar:file:/C:/foo/bar/baz.jar!/mpconfig/a.properties
* jar:file:/C:/foo/bar/baz.jar!/mpconfig/b.properties
* file:/C:/for/bar/target/classes/mpconfig/c.text
* </pre>
* @param targetDir Path to search for resources. Do not add `/` at the beginning
* @return URL of the target resource. Returns an empty list if not applicable.
* @throws IOException IO error occurs.
*/
public static List<URL> findResoucePathUnder(String targetDir) throws IOException {
return findResoucePathUnder(
targetDir,
(t) -> true, // NOP filter
Thread.currentThread().getContextClassLoader());
}
/**
* Get the class resource under the path specified by the argument by URL.
* @see #findResoucePathUnder(String)
*/
public static List<URL> findResoucePathUnder(String targetDir, Predicate<Object> fileFilter, ClassLoader classLoader) throws IOException {
// TargetDir search and Split URL by protocol
Enumeration<URL> dirs = classLoader.getResources(targetDir);
Map<String, List<URL>> urlMap = Collections.list(dirs).stream()
.collect(Collectors.groupingBy(URL::getProtocol));
// Search under the directory for each protocol
List<URL> targetUrls = new ArrayList<>();
if (urlMap.containsKey("jar")) {
List<URL> tempUrls = findResourcePathUnderInJar(targetDir, urlMap.get("jar"), fileFilter);
targetUrls.addAll(tempUrls);
}
if (urlMap.containsKey("file")) {
List<URL> tempUrls = findResourcePathUnderInFileSystem(urlMap.get("file"), fileFilter);
targetUrls.addAll(tempUrls);
}
return targetUrls;
}
/**
* Make a jar file on the file system based on the resource URL in the jar.
* @param resourceUrl Resource URL in the jar.
* @return File object in jar.
*/
public static File toJarFileFromResourceUrl(URL resourceUrl) {
return toFileFromUrlString(extractFilePathStringOfUrl(resourceUrl));
}
/**
* Extract the character string of the Jar file path part from the resource URL in the jar.
* @param resourceUrl Resource URL in the jar.
* @return String of FilePath.
*/
public static String extractFilePathStringOfUrl(URL resourceUrl) {
return resourceUrl.getPath().substring(0, resourceUrl.getPath().indexOf('!'));
}
// ----------------------------------------------------- private methods
private static List<URL> findResourcePathUnderInJar(String targetDir, List<URL> inJarUrls, Predicate<Object> fileFilter) throws IOException {
// --- Conversion to File ---
// ex) targetDir = META-INF
// input-> jar:file:/C:/foo/bar/baz.jar!/MATA-INF/...
// step1-> file:/C:/foo/bar/baz.jar
// step2-> new URL(file:/C:/foo/bar/baz.jar).toURI()
// step3-> new File(uri)
// -----------------------
List<File> jarFiles = inJarUrls.stream()
.map(ResourceUtils::toJarFileFromResourceUrl)
.toList();
// --- Scan in JarFile ---
// input-> file(C:\foo\bar\baz.jar)
// step1-> new JarFile(file).entries()
// step2-> entry.getName() -> "META-INF/..."
// step3-> C:\foo\bar\baz.jar -> C:/foo/bar/baz.jar
// step4-> "jar:file:/" + "C:/foo/bar/baz.jar" + "!/" + "META-INF/..."
// -----------------------
List<URL> targetUrls = new ArrayList<>();
for (File jarFile : jarFiles) {
List<URL> urls = null;
try (var jar = new JarFile(jarFile)) {
urls = jar.stream()
.filter(not(JarEntry::isDirectory))
.filter(entry -> entry.getName().startsWith(targetDir))
.filter(fileFilter)
.map(entry -> {
String filePath = jarFile.toString().replace(File.separator, "/");
if (!filePath.startsWith("/")) {
filePath = "/" + filePath; // for Windows
}
return "jar:file:" + filePath + "!/" + entry.getName();})
.map(ResourceUtils::toUrl)
.toList();
}
targetUrls.addAll(urls);
}
return targetUrls;
}
private static List<URL> findResourcePathUnderInFileSystem(List<URL> inFsUrls, Predicate<Object> fileFilter) throws IOException {
// --- Scan in classpath on FileSystem ---
// ex) targetDir = META-INF
// input-> file:/C:/for/bar/target/classes/META-INF
// setp1-> scan uner "file:/C:/for/bar/target/classes/META-INF"
// setp2-> add files that match the filters to targetUrls
// ---------------------------------------
List<URL> targetUrls = new ArrayList<>();
for (var targetUrl : inFsUrls) {
var targetPath = Paths.get(toUrl(targetUrl));
if (Files.isDirectory(targetPath)) {
Files.walkFileTree(targetPath, new TargetFileCollector(targetUrls, fileFilter)); // targetUrl is IN/OUT Parameter.
} else {
targetUrls.add(toUrl(targetPath)); // targetPath is file.
}
}
return targetUrls;
}
private static File toFileFromUrlString(String urlSpec) {
try {
return new File(new URL(urlSpec).toURI());
} catch (MalformedURLException | URISyntaxException e) {
throw new IllegalStateException(e);
}
}
private static URL toUrl(String urlSpec) {
try {
return new URL(urlSpec);
} catch (MalformedURLException e) {
throw new IllegalStateException(e);
}
}
private static URL toUrl(Path path) {
try {
return path.toUri().toURL();
} catch (MalformedURLException e) {
throw new IllegalStateException(e);
}
}
private static URI toUrl(URL url) {
try {
return url.toURI();
} catch (URISyntaxException e) {
throw new IllegalStateException(e);
}
}
static class TargetFileCollector extends SimpleFileVisitor<Path> {
private List<URL> collectHolder;
private Predicate<Object> fileFilter;
TargetFileCollector(List<URL> collectHolder, Predicate<Object> fileFilter) {
this.collectHolder = collectHolder;
this.fileFilter = fileFilter;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (fileFilter.test(file)) {
collectHolder.add(toUrl(file));
}
return FileVisitResult.CONTINUE;
}
}
}