ConfigTreePropertySource Class — spring-boot Architecture
Architecture documentation for the ConfigTreePropertySource class in ConfigTreePropertySource.java from the spring-boot codebase.
Entity Profile
Relationship Graph
Source Code
core/spring-boot/src/main/java/org/springframework/boot/env/ConfigTreePropertySource.java lines 79–380
public class ConfigTreePropertySource extends EnumerablePropertySource<Path>
implements PropertySourceInfo, OriginLookup<String> {
private static final int MAX_DEPTH = 100;
private final Map<String, PropertyFile> propertyFiles;
private final String[] names;
private final Set<Option> options;
/**
* Create a new {@link ConfigTreePropertySource} instance.
* @param name the name of the property source
* @param sourceDirectory the underlying source directory
*/
public ConfigTreePropertySource(String name, Path sourceDirectory) {
this(name, sourceDirectory, EnumSet.noneOf(Option.class));
}
/**
* Create a new {@link ConfigTreePropertySource} instance.
* @param name the name of the property source
* @param sourceDirectory the underlying source directory
* @param options the property source options
*/
public ConfigTreePropertySource(String name, Path sourceDirectory, Option... options) {
this(name, sourceDirectory, EnumSet.copyOf(Arrays.asList(options)));
}
private ConfigTreePropertySource(String name, Path sourceDirectory, Set<Option> options) {
super(name, sourceDirectory);
Assert.isTrue(Files.exists(sourceDirectory),
() -> "'sourceDirectory' [%s] must exist".formatted(sourceDirectory));
Assert.isTrue(Files.isDirectory(sourceDirectory),
() -> "'sourceDirectory' [%s] must be a directory".formatted(sourceDirectory));
this.propertyFiles = PropertyFile.findAll(sourceDirectory, options);
this.options = options;
this.names = StringUtils.toStringArray(this.propertyFiles.keySet());
}
@Override
public String[] getPropertyNames() {
return this.names.clone();
}
@Override
public @Nullable Value getProperty(String name) {
PropertyFile propertyFile = this.propertyFiles.get(name);
return (propertyFile != null) ? propertyFile.getContent() : null;
}
@Override
public @Nullable Origin getOrigin(String name) {
PropertyFile propertyFile = this.propertyFiles.get(name);
return (propertyFile != null) ? propertyFile.getOrigin() : null;
}
@Override
public boolean isImmutable() {
return !this.options.contains(Option.ALWAYS_READ);
}
/**
* Property source options.
*/
public enum Option {
/**
* Always read the value of the file when accessing the property value. When this
* option is not set the property source will cache the value when it's first
* read.
*/
ALWAYS_READ,
/**
* Convert file and directory names to lowercase.
*/
USE_LOWERCASE_NAMES,
/**
* Automatically attempt trim trailing new-line characters.
*/
AUTO_TRIM_TRAILING_NEW_LINE
}
/**
* A value returned from the property source which exposes the contents of the
* property file. Values can either be treated as {@link CharSequence} or as an
* {@link InputStreamSource}.
*/
public interface Value extends CharSequence, InputStreamSource {
}
/**
* A single property file that was found when the source was created.
*/
private static final class PropertyFile {
private static final Location START_OF_FILE = new Location(0, 0);
private final Path path;
private final FileSystemResource resource;
private final Origin origin;
private final @Nullable PropertyFileContent cachedContent;
private final boolean autoTrimTrailingNewLine;
private PropertyFile(Path path, Set<Option> options) {
this.path = path;
this.resource = new FileSystemResource(path);
this.origin = new TextResourceOrigin(this.resource, START_OF_FILE);
this.autoTrimTrailingNewLine = options.contains(Option.AUTO_TRIM_TRAILING_NEW_LINE);
this.cachedContent = options.contains(Option.ALWAYS_READ) ? null
: new PropertyFileContent(path, this.resource, this.origin, true, this.autoTrimTrailingNewLine);
}
PropertyFileContent getContent() {
if (this.cachedContent != null) {
return this.cachedContent;
}
return new PropertyFileContent(this.path, this.resource, this.origin, false, this.autoTrimTrailingNewLine);
}
Origin getOrigin() {
return this.origin;
}
static Map<String, PropertyFile> findAll(Path sourceDirectory, Set<Option> options) {
try {
Map<String, PropertyFile> propertyFiles = new TreeMap<>();
try (Stream<Path> pathStream = Files.find(sourceDirectory, MAX_DEPTH, PropertyFile::isPropertyFile,
FileVisitOption.FOLLOW_LINKS)) {
pathStream.forEach((path) -> {
String name = getName(sourceDirectory.relativize(path));
if (StringUtils.hasText(name)) {
if (options.contains(Option.USE_LOWERCASE_NAMES)) {
name = name.toLowerCase(Locale.getDefault());
}
propertyFiles.put(name, new PropertyFile(path, options));
}
});
}
return Collections.unmodifiableMap(propertyFiles);
}
catch (IOException ex) {
throw new IllegalStateException("Unable to find files in '" + sourceDirectory + "'", ex);
}
}
private static boolean isPropertyFile(Path path, BasicFileAttributes attributes) {
return !hasHiddenPathElement(path) && (attributes.isRegularFile() || attributes.isSymbolicLink());
}
private static boolean hasHiddenPathElement(Path path) {
for (Path element : path) {
if (element.toString().startsWith("..")) {
return true;
}
}
return false;
}
private static String getName(Path relativePath) {
int nameCount = relativePath.getNameCount();
if (nameCount == 1) {
return relativePath.toString();
}
StringBuilder name = new StringBuilder();
for (int i = 0; i < nameCount; i++) {
name.append((i != 0) ? "." : "");
name.append(relativePath.getName(i));
}
return name.toString();
}
}
/**
* The contents of a found property file.
*/
private static final class PropertyFileContent implements Value, OriginProvider {
private final Path path;
private final Lock resourceLock = new ReentrantLock();
private final Resource resource;
private final Origin origin;
private final boolean cacheContent;
private final boolean autoTrimTrailingNewLine;
private volatile byte @Nullable [] content;
private PropertyFileContent(Path path, Resource resource, Origin origin, boolean cacheContent,
boolean autoTrimTrailingNewLine) {
this.path = path;
this.resource = resource;
this.origin = origin;
this.cacheContent = cacheContent;
this.autoTrimTrailingNewLine = autoTrimTrailingNewLine;
}
@Override
public Origin getOrigin() {
return this.origin;
}
@Override
public int length() {
return toString().length();
}
@Override
public char charAt(int index) {
return toString().charAt(index);
}
@Override
public CharSequence subSequence(int start, int end) {
return toString().subSequence(start, end);
}
@Override
public String toString() {
String string = new String(getBytes());
if (this.autoTrimTrailingNewLine) {
string = autoTrimTrailingNewLine(string);
}
return string;
}
private String autoTrimTrailingNewLine(String string) {
if (!string.endsWith("\n")) {
return string;
}
int numberOfLines = 0;
for (int i = 0; i < string.length(); i++) {
char ch = string.charAt(i);
if (ch == '\n') {
numberOfLines++;
}
}
if (numberOfLines > 1) {
return string;
}
return (string.endsWith("\r\n")) ? string.substring(0, string.length() - 2)
: string.substring(0, string.length() - 1);
}
@Override
public InputStream getInputStream() throws IOException {
if (!this.cacheContent) {
assertStillExists();
return this.resource.getInputStream();
}
return new ByteArrayInputStream(getBytes());
}
private byte[] getBytes() {
try {
if (!this.cacheContent) {
assertStillExists();
return FileCopyUtils.copyToByteArray(this.resource.getInputStream());
}
byte[] content = this.content;
if (content == null) {
assertStillExists();
this.resourceLock.lock();
try {
content = this.content;
if (content == null) {
content = FileCopyUtils.copyToByteArray(this.resource.getInputStream());
this.content = content;
}
}
finally {
this.resourceLock.unlock();
}
}
return content;
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
private void assertStillExists() {
Assert.state(Files.exists(this.path), () -> "The property file '" + this.path + "' no longer exists");
}
}
}
Domain
Source
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free