/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.core.io.scan;
import io.micronaut.core.io.ResourceLoader;
import io.micronaut.core.util.StringUtils;
import io.micronaut.core.util.clhm.ConcurrentLinkedHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.*;
import java.util.*;
import java.util.stream.Stream;
Loads resources from the classpath.
Author: James Kleeh, graemerocher Since: 1.0
/**
* Loads resources from the classpath.
*
* @author James Kleeh
* @author graemerocher
* @since 1.0
*/
public class DefaultClassPathResourceLoader implements ClassPathResourceLoader {
private static final Logger LOG = LoggerFactory.getLogger(DefaultClassPathResourceLoader.class);
private final ClassLoader classLoader;
private final String basePath;
private final Map<String, Boolean> isDirectoryCache = new ConcurrentLinkedHashMap.Builder<String, Boolean>()
.maximumWeightedCapacity(50).build();
Default constructor.
Params: - classLoader – The class loader for loading resources
/**
* Default constructor.
*
* @param classLoader The class loader for loading resources
*/
public DefaultClassPathResourceLoader(ClassLoader classLoader) {
this(classLoader, null);
}
Use when resources should have a standard base path.
Params: - classLoader – The class loader for loading resources
- basePath – The path to look for resources under
/**
* Use when resources should have a standard base path.
*
* @param classLoader The class loader for loading resources
* @param basePath The path to look for resources under
*/
public DefaultClassPathResourceLoader(ClassLoader classLoader, String basePath) {
this.classLoader = classLoader;
this.basePath = normalize(basePath);
}
Obtains a resource as a stream.
Params: - path – The path
Returns: An optional resource
/**
* Obtains a resource as a stream.
*
* @param path The path
* @return An optional resource
*/
@Override
public Optional<InputStream> getResourceAsStream(String path) {
if (!isDirectory(path)) {
return Optional.ofNullable(classLoader.getResourceAsStream(prefixPath(path)));
}
return Optional.empty();
}
Obtains a resource URL.
Params: - path – The path
Returns: An optional resource
/**
* Obtains a resource URL.
*
* @param path The path
* @return An optional resource
*/
@Override
public Optional<URL> getResource(String path) {
boolean isDirectory = isDirectory(path);
if (!isDirectory) {
URL url = classLoader.getResource(prefixPath(path));
return Optional.ofNullable(url);
}
return Optional.empty();
}
Obtains a stream of resource URLs.
Params: - path – The path
Returns: A resource stream
/**
* Obtains a stream of resource URLs.
*
* @param path The path
* @return A resource stream
*/
@Override
public Stream<URL> getResources(String path) {
Enumeration<URL> all;
try {
all = classLoader.getResources(prefixPath(path));
} catch (IOException e) {
return Stream.empty();
}
Stream.Builder<URL> builder = Stream.builder();
while (all.hasMoreElements()) {
URL url = all.nextElement();
builder.accept(url);
}
return builder.build();
}
Returns: The class loader used to retrieve resources
/**
* @return The class loader used to retrieve resources
*/
@Override
public ClassLoader getClassLoader() {
return classLoader;
}
Params: - basePath – The path to load resources
Returns: The resouce loader
/**
* @param basePath The path to load resources
* @return The resouce loader
*/
@Override
public ResourceLoader forBase(String basePath) {
return new DefaultClassPathResourceLoader(classLoader, basePath);
}
@SuppressWarnings("MagicNumber")
private String normalize(String path) {
if (path != null) {
if (path.startsWith("classpath:")) {
path = path.substring(10);
}
if (path.startsWith("/")) {
path = path.substring(1);
}
if (!path.endsWith("/") && StringUtils.isNotEmpty(path)) {
path = path + "/";
}
}
return path;
}
@SuppressWarnings("ConstantConditions")
private boolean isDirectory(String path) {
return isDirectoryCache.computeIfAbsent(path, s -> {
URL url = classLoader.getResource(prefixPath(path));
if (url != null) {
try {
URI uri = url.toURI();
Path pathObject;
synchronized (DefaultClassPathResourceLoader.class) {
if (uri.getScheme().equals("jar")) {
FileSystem fileSystem = null;
try {
try {
fileSystem = FileSystems.getFileSystem(uri);
} catch (FileSystemNotFoundException e) {
//no-op
}
if (fileSystem == null || !fileSystem.isOpen()) {
fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap(), classLoader);
}
pathObject = fileSystem.getPath(path);
return pathObject == null || Files.isDirectory(pathObject);
} finally {
if (fileSystem != null && fileSystem.isOpen()) {
try {
fileSystem.close();
} catch (IOException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Error shutting down JAR file system [" + fileSystem + "]: " + e.getMessage(), e);
}
}
}
}
} else if (uri.getScheme().equals("file")) {
pathObject = Paths.get(uri);
return pathObject == null || Files.isDirectory(pathObject);
}
}
} catch (URISyntaxException | IOException | ProviderNotFoundException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Error establishing whether path is a directory: " + e.getMessage(), e);
}
}
}
return path.indexOf('.') == -1; // fallback to less sophisticated approach
});
}
@SuppressWarnings("MagicNumber")
private String prefixPath(String path) {
if (path.startsWith("classpath:")) {
path = path.substring(10);
}
if (basePath != null) {
if (path.startsWith("/")) {
return basePath + path.substring(1);
}
return basePath + path;
}
return path;
}
}