Home / Class/ ChangelogWriter Class — spring-boot Architecture

ChangelogWriter Class — spring-boot Architecture

Architecture documentation for the ChangelogWriter class in ChangelogWriter.java from the spring-boot codebase.

Entity Profile

Relationship Graph

Source Code

configuration-metadata/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriter.java lines 51–245

class ChangelogWriter implements AutoCloseable {

	private static final Comparator<ConfigurationMetadataProperty> COMPARING_ID = Comparator
		.comparing(ConfigurationMetadataProperty::getId);

	private final PrintWriter out;

	ChangelogWriter(File out) throws IOException {
		this(new FileWriter(out));
	}

	ChangelogWriter(Writer out) {
		this.out = new PrintWriter(out);
	}

	void write(Changelog changelog) {
		String oldVersionNumber = changelog.oldVersionNumber();
		String newVersionNumber = changelog.newVersionNumber();
		Map<DifferenceType, List<Difference>> differencesByType = collateByType(changelog);
		write("Configuration property changes between `%s` and `%s`%n", oldVersionNumber, newVersionNumber);
		write("%n%n%n== Default Changed in %s%n%n", newVersionNumber);
		writeDefaultChanged(differencesByType.get(DifferenceType.DEFAULT_CHANGED));
		write("%n%n%n== Deprecated in %s%n%n", newVersionNumber);
		writeDeprecated(differencesByType.get(DifferenceType.DEPRECATED));
		write("%n%n%n== Added in %s%n%n", newVersionNumber);
		writeAdded(differencesByType.get(DifferenceType.ADDED));
		write("%n%n%n== Removed in %s%n%n", newVersionNumber);
		writeRemoved(differencesByType.get(DifferenceType.DELETED), differencesByType.get(DifferenceType.DEPRECATED));
	}

	private Map<DifferenceType, List<Difference>> collateByType(Changelog differences) {
		Map<DifferenceType, List<Difference>> byType = new HashMap<>();
		for (DifferenceType type : DifferenceType.values()) {
			byType.put(type, new ArrayList<>());
		}
		for (Difference difference : differences.differences()) {
			byType.get(difference.type()).add(difference);
		}
		return byType;
	}

	private void writeDeprecated(List<Difference> differences) {
		List<Difference> rows = sortProperties(differences, Difference::newProperty).stream()
			.filter(this::isDeprecatedInRelease)
			.toList();
		writeTable("| Key | Replacement | Reason", rows, this::writeDeprecated);
	}

	private void writeDeprecated(Difference difference) {
		writeDeprecatedPropertyRow(difference.newProperty());
	}

	private void writeDefaultChanged(List<Difference> differences) {
		List<Difference> rows = sortProperties(differences, Difference::newProperty);
		writeTable("| Key | Old Default | New Default", rows, this::writeDefaultChanged);
	}

	private void writeDefaultChanged(Difference difference) {
		writeCell(monospace(difference.newProperty().getId()));
		writeCell(monospace(asString(difference.oldProperty().getDefaultValue())));
		writeCell(monospace(asString(difference.newProperty().getDefaultValue())));
	}

	private void writeAdded(List<Difference> differences) {
		List<Difference> rows = sortProperties(differences, Difference::newProperty);
		writeTable("| Key | Default value | Description", rows, this::writeAdded);
	}

	private void writeAdded(Difference difference) {
		ConfigurationMetadataProperty property = difference.newProperty();
		writeCell(monospace(property.getId()) + (property.isDeprecated() ? " (deprecated)" : ""));
		writeCell(monospace(asString(property.getDefaultValue())));
		writeCell(property.getShortDescription());
	}

	private void writeRemoved(List<Difference> deleted, List<Difference> deprecated) {
		List<Difference> rows = getRemoved(deleted, deprecated);
		writeTable("| Key | Replacement | Reason", rows, this::writeRemoved);
	}

	private List<Difference> getRemoved(List<Difference> deleted, List<Difference> deprecated) {
		List<Difference> result = new ArrayList<>(deleted);
		deprecated.stream().filter(Predicate.not(this::isDeprecatedInRelease)).forEach(result::remove);
		return sortProperties(result,
				(difference) -> getFirstNonNull(difference, Difference::oldProperty, Difference::newProperty));
	}

	private void writeRemoved(Difference difference) {
		writeDeprecatedPropertyRow(getFirstNonNull(difference, Difference::newProperty, Difference::oldProperty));
	}

	private List<Difference> sortProperties(List<Difference> differences,
			Function<Difference, ConfigurationMetadataProperty> extractor) {
		return differences.stream().sorted(Comparator.comparing(extractor, COMPARING_ID)).toList();
	}

	@SafeVarargs
	@SuppressWarnings("varargs")
	private <T, P> P getFirstNonNull(T t, Function<T, P>... extractors) {
		return Stream.of(extractors)
			.map((extractor) -> extractor.apply(t))
			.filter(Objects::nonNull)
			.findFirst()
			.orElse(null);
	}

	private void writeTable(String header, List<Difference> rows, Consumer<Difference> action) {
		if (rows.isEmpty()) {
			write("_None_.%n");
		}
		else {
			writeTableBreak();
			write(header + "%n%n");
			for (Iterator<Difference> iterator = rows.iterator(); iterator.hasNext();) {
				action.accept(iterator.next());
				write((!iterator.hasNext()) ? null : "%n");
			}
			writeTableBreak();
		}
	}

	private void writeTableBreak() {
		write("|======================%n");
	}

	private void writeDeprecatedPropertyRow(ConfigurationMetadataProperty property) {
		Deprecation deprecation = (property.getDeprecation() != null) ? property.getDeprecation() : new Deprecation();
		writeCell(monospace(property.getId()));
		writeCell(monospace(deprecation.getReplacement()));
		writeCell(getFirstSentence(deprecation.getReason()));
	}

	private String getFirstSentence(String text) {
		if (text == null) {
			return null;
		}
		int dot = text.indexOf('.');
		if (dot != -1) {
			BreakIterator breakIterator = BreakIterator.getSentenceInstance(Locale.US);
			breakIterator.setText(text);
			String sentence = text.substring(breakIterator.first(), breakIterator.next()).trim();
			return removeSpaceBetweenLine(sentence);
		}
		String[] lines = text.split(System.lineSeparator());
		return lines[0].trim();
	}

	private String removeSpaceBetweenLine(String text) {
		String[] lines = text.split(System.lineSeparator());
		return Arrays.stream(lines).map(String::trim).collect(Collectors.joining(" "));
	}

	private boolean isDeprecatedInRelease(Difference difference) {
		Deprecation deprecation = difference.newProperty().getDeprecation();
		return (deprecation != null) && (deprecation.getLevel() != Deprecation.Level.ERROR);
	}

	private String monospace(String value) {
		return (value != null) ? "`%s`".formatted(value) : null;
	}

	private void writeCell(String content) {
		if (content == null) {
			write("|%n");
		}
		else {
			String escaped = escapeForTableCell(content);
			write("| %s%n".formatted(escaped));
		}
	}

	private String escapeForTableCell(String content) {
		return content.replace("|", "\\|");
	}

	private void write(String format, Object... args) {
		if (format != null) {
			Object[] strings = Arrays.stream(args).map(this::asString).toArray();
			this.out.append(format.formatted(strings));
		}
	}

	private String asString(Object value) {
		if (value instanceof Object[] array) {
			return Stream.of(array).map(this::asString).collect(Collectors.joining(", "));
		}
		return (value != null) ? value.toString() : null;
	}

	@Override
	public void close() {
		this.out.close();
	}

}

Domain

Analyze Your Own Codebase

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

Try Supermodel Free