package ch.inftec.ju.util;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
public class JuUrl {
/**
* Gets the resource with the specified name, using the ClassLoader to
* resolve the resource.
* @param resourceName Absolute name, not starting with a '/'
* @return (First) found resource as URL or null if none was found
*/
public static URL resource(String resourceName) {
return JuUrl.resource().get(resourceName);
}
/**
* Gets a resource URL relative to the specified class.
* @param resourceName Resource name
* @param relativeClass Class used to resolve the resource
* @return (First) found resource as URL or null if none was found
*/
public static URL resourceRelativeTo(String resourceName, Class<?> relativeClass) {
return JuUrl.resource().relativeTo(relativeClass).get(resourceName);
}
/**
* Gets the resource with the specified name, making sure that there is only one resource with the name
* and that it exists.
* @param resourceName Resource name
* @return URL to resource
* @throws JuRuntimeException If resource doesn't exist
*/
public static URL singleResource(String resourceName) {
return JuUrl.resource().single().exceptionIfNone().get(resourceName);
}
/**
* Converts the specified path to an URL, wrapping any exception into a
* runtime exception.
* @param p Path
* @return Path as URL
*/
public static URL toUrl(Path p) {
try {
return p.toUri().toURL();
} catch (Exception ex) {
throw new JuRuntimeException("Couldn't convert path '%s' to URL", ex, p);
}
}
/**
* Converts the specified URL to a Path instance.
* @param url URL
* @return Path instance
*/
public static Path toPath(URL url) {
try {
return Paths.get(url.toURI());
} catch (Exception ex) {
throw new JuRuntimeException("Couldn't convert URL %s to Path", ex, url);
}
}
/**
* Gets a Path instance to an existing file.
* @param path Path to file
* @return Path instance
* @throws JuRuntimeException If the file doesn't exist
*/
public static Path existingFile(String path) {
return JuUrl.path().file().exists().get(path);
}
/**
* Gets a Path instance to an existing folder.
* @param path Path to folder
* @return Path instance
* @throws JuRuntimeException If the folder doesn't exist
*/
public static Path existingFolder(String path) {
return JuUrl.path().directory().exists().get(path);
}
/**
* Gets an URL to an existing resource. If none is found, an exception will be
* thrown.
* <p>
* If multiple resources with the specified name/path exist, the first found is returned.
* @param resourceName Resource name
* @return URL to resource
*/
public static URL existingResource(String resourceName) {
return JuUrl.resource().exceptionIfNone().get(resourceName);
}
/**
*Gets a PathUrlBuilder that can be used to configure and perform path lookup.
* @return PathUrlBuilder instance
*/
public static PathUrlBuilder path() {
return new PathUrlBuilder();
}
/**
* Helper class to resolve paths.
* @author Martin
*
*/
public static class PathUrlBuilder {
private boolean file;
private boolean directory;
private boolean exists;
private String relativeToFirst;
private String[] relativeToMore = new String[0];
/**
* Sets the flag that the path must be a file.
* <p>
* Default is false which means that it needn't be a file (but CAN be).
* @return This builder
*/
public PathUrlBuilder file() {
this.file = true;
return this;
}
/**
* Sets the flag that the path must be a directory.
* <p>
* Defaults to false which means that it needn't be a directory (but CAN be).
* @return This builder
*/
public PathUrlBuilder directory() {
this.directory = true;
return this;
}
/**
* Sets the modified that the path must exist. If not, an exception will be thrown.
* @return This builder
*/
public PathUrlBuilder exists() {
this.exists = true;
return this;
}
/**
* Sets the relative paths to resolve the final path.
* @param first First path part
* @param more Optional additional path parts
* @return This builder
*/
public PathUrlBuilder relativeTo(String first, String... more) {
this.relativeToFirst = first;
this.relativeToMore = more;
return this;
}
/**
* Gets the specified path, using the configuration of the builder.
* @param path Path
* @return Path instance. If exists is set, an exception will be thrown if the path doesn't exist
*/
public Path get(String path) {
Path p = null;
if (StringUtils.isEmpty(this.relativeToFirst)) {
p = Paths.get(path);
} else {
String moreString[] = Arrays.copyOf(this.relativeToMore, this.relativeToMore.length + 1);
moreString[moreString.length - 1] = path;
p = Paths.get(this.relativeToFirst, moreString);
}
if (this.exists && !Files.exists(p)) {
throw new JuRuntimeException("Path doesn't exist: %s (absolute: %s)", p, p.toAbsolutePath());
}
if (this.file && !Files.isRegularFile(p)) {
throw new JuRuntimeException("Path is not a file: %s (absolute: %s)", p, p.toAbsolutePath());
}
if (this.directory && !Files.isDirectory(p)) {
throw new JuRuntimeException("Path is not a directory: %s (absolute: %s)", p, p.toAbsolutePath());
}
return p;
}
/**
* Checks if the specified path is an existing file.
* @param path Path to file
* @return True if path is an existing file, false otherwise
*/
public boolean isExistingFile(Path path) {
return Files.exists(path) && Files.isRegularFile(path);
}
/**
* Checks if the specified path is an existing directory.
* @param path Path to directory
* @return True if path is an existing directory, false otherwise
*/
public boolean isExistingDirectory(Path path) {
return Files.exists(path) && Files.isDirectory(path);
}
}
/**
* Gets a ResourceUrlBuilder that can be used to configure and perform resource lookup.
* @return ResourceUrlBuilder instance
*/
public static ResourceUrlBuilder resource() {
return new ResourceUrlBuilder();
}
/**
* Builder to lookup resources.
* @author Martin
*
*/
public static class ResourceUrlBuilder {
private Class<?> relativeClass;
private boolean single = false;
private boolean exceptionIfNone = false;
/**
* Resolves the resource relative to the specified class, using the method
* Class.getResource.
* <p>
* If the resource starts with an '/', the path is absolute.
* @param clazz Relative class
* @return This builder
*/
public ResourceUrlBuilder relativeTo(Class<?> clazz) {
this.relativeClass = clazz;
return this;
}
/**
* Modifier for the get method to make sure that a single resource is found.
* <p>
* If multiple resources are found, an exception is thrown.
* <p>
* Default is false.
* @return This builder
*/
public ResourceUrlBuilder single() {
this.single = true;
return this;
}
/**
* Modified to throw an exception if no resource is found.
* <p>
* Default is false, i.e. null is returned if no resource is found
* @retuen This builder
*/
public ResourceUrlBuilder exceptionIfNone() {
this.exceptionIfNone = true;
return this;
}
/**
* Sets the exceptionIfNone flag.
* @param exceptionIfNone If true, an exception is thrown if the resource doesn't exist
* @return This builder
*/
public ResourceUrlBuilder exceptionIfNone(boolean exceptionIfNone) {
this.exceptionIfNone = exceptionIfNone;
return this;
}
/**
* Gets a list of all resources on the classpath using the JuUrl classloader.
* <p>
* Paths are always absolute and must not start with a '/'.
* <p>
* Examples:<ul>
* <li>log4j.xml</li>
* <li>META-INF/persistence.xml</li>
* </ul>
* @param resourceName Resource name
* @return List of all resources with the specified name, as returned by the ClassLoader.getResources method. If none
* are found, an empty list is returned
*/
public List<URL> getAll(String resourceName) {
try {
Enumeration<URL> resourcesEnum = IOUtil.class.getClassLoader().getResources(resourceName);
List<URL> resources = new ArrayList<>();
while (resourcesEnum.hasMoreElements()) {
resources.add(resourcesEnum.nextElement());
}
return resources;
} catch (Exception ex) {
throw new JuRuntimeException("Couldn't lookup resource " + resourceName, ex);
}
}
/**
* Gets the resource with the specified name, taking the configuration of the
* builder into account.
* @param resourceName Resource name
* @return URL or null if none was found and exceptionIfNone is not set. May also throw an exception
* if multiples are found and single is set.
*/
public URL get(String resourceName) {
URL url = null;
if (this.relativeClass != null) {
url = this.relativeClass.getResource(resourceName);
} else {
List<URL> urls = this.getAll(resourceName);
if (this.single && urls.size() > 1) {
XString xs = new XString("Found more than 1 resource with name %s:", resourceName);
xs.increaseIndent();
for (URL u : urls) xs.addLine(u.toString());
throw new JuRuntimeException(xs.toString());
} else if (urls.size() > 0) {
url = urls.get(0);
}
}
if (url == null && this.exceptionIfNone) {
throw new JuRuntimeException("Resource not found: %s", resourceName);
} else {
return url;
}
}
}
}