Home / Class/ ConfigTreePropertySource Class — spring-boot Architecture

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

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free