From 1bf334ae0fcc33e59d39598ae87e597548fad19c Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Fri, 30 Jun 2023 22:15:19 +0100 Subject: [PATCH] Polish config metadata changelog generator See gh-21486 --- .../build.gradle | 2 +- .../changelog/Changelog.java | 64 +++++ .../changelog/ChangelogGenerator.java | 79 ++++++ .../changelog/ChangelogWriter.java | 224 ++++++++++++++++++ ...nfigurationMetadataChangelogGenerator.java | 56 ----- .../ConfigurationMetadataChangelogWriter.java | 204 ---------------- .../changelog/ConfigurationMetadataDiff.java | 109 --------- .../changelog/Difference.java | 52 ++++ .../changelog/DifferenceType.java | 42 ++++ .../NamedConfigurationMetadataRepository.java | 80 ------- .../changelog/ChangelogGeneratorTests.java | 68 ++++++ .../changelog/ChangelogTests.java | 73 ++++++ .../changelog/ChangelogWriterTests.java | 45 ++++ .../ConfigurationMetadataDiffTests.java | 92 ------- .../changelog/TestChangelog.java | 52 ++++ .../src/test/resources/sample-2.0.json | 4 +- .../src/test/resources/sample.adoc | 32 +++ 17 files changed, 735 insertions(+), 543 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Changelog.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGenerator.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriter.java delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogGenerator.java delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogWriter.java delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiff.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Difference.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/DifferenceType.java delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/NamedConfigurationMetadataRepository.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGeneratorTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriterTests.java delete mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiffTests.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/TestChangelog.java create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample.adoc diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/build.gradle b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/build.gradle index 4d5c517e90..186a2cff85 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/build.gradle +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/build.gradle @@ -50,7 +50,7 @@ if (project.hasProperty("oldVersion") && project.hasProperty("newVersion")) { inputs.files(prepareOldMetadata, prepareNewMetadata) outputs.file(project.file("build/configuration-metadata-changelog.adoc")) classpath = sourceSets.main.runtimeClasspath - mainClass = 'org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataChangelogGenerator' + mainClass = 'org.springframework.boot.configurationmetadata.changelog.ChangelogGenerator' if (project.hasProperty("oldVersion") && project.hasProperty("newVersion")) { args = [project.file("build/configuration-metadata-diff/$oldVersion"), project.file("build/configuration-metadata-diff/$newVersion"), project.file("build/configuration-metadata-changelog.adoc")] } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Changelog.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Changelog.java new file mode 100644 index 0000000000..964298fe56 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Changelog.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2023 the original author or 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 org.springframework.boot.configurationmetadata.changelog; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; + +/** + * A changelog containing differences computed from two repositories of configuration + * metadata. + * + * @param oldVersionNumber the name of the old version + * @param newVersionNumber the name of the new version + * @param differences the differences + * @author Stephane Nicoll + * @author Andy Wilkinson + * @author Phillip Webb + */ +record Changelog(String oldVersionNumber, String newVersionNumber, List differences) { + + static Changelog of(String oldVersionNumber, ConfigurationMetadataRepository oldMetadata, String newVersionNumber, + ConfigurationMetadataRepository newMetadata) { + return new Changelog(oldVersionNumber, newVersionNumber, computeDifferences(oldMetadata, newMetadata)); + } + + static List computeDifferences(ConfigurationMetadataRepository oldMetadata, + ConfigurationMetadataRepository newMetadata) { + List seenIds = new ArrayList<>(); + List differences = new ArrayList<>(); + for (ConfigurationMetadataProperty oldProperty : oldMetadata.getAllProperties().values()) { + String id = oldProperty.getId(); + seenIds.add(id); + ConfigurationMetadataProperty newProperty = newMetadata.getAllProperties().get(id); + Difference difference = Difference.compute(oldProperty, newProperty); + if (difference != null) { + differences.add(difference); + } + } + for (ConfigurationMetadataProperty newProperty : newMetadata.getAllProperties().values()) { + if ((!seenIds.contains(newProperty.getId())) && (!newProperty.isDeprecated())) { + differences.add(new Difference(DifferenceType.ADDED, null, newProperty)); + } + } + return List.copyOf(differences); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGenerator.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGenerator.java new file mode 100644 index 0000000000..9d1ee1d622 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGenerator.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012-2023 the original author or 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 org.springframework.boot.configurationmetadata.changelog; + +import java.io.File; +import java.io.IOException; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder; + +/** + * Generates a configuration metadata changelog. Requires three arguments: + * + *
    + *
  1. The path of a directory containing jar files of the old version + *
  2. The path of a directory containing jar files of the new version + *
  3. The path of a file to which the asciidoc changelog will be written + *
+ * + * The name of each directory will be used as version numbers in generated changelog. + * + * @author Andy Wilkinson + * @author Phillip Webb + * @since 3.2.0 + */ +public final class ChangelogGenerator { + + private ChangelogGenerator() { + } + + public static void main(String[] args) throws IOException { + generate(new File(args[0]), new File(args[1]), new File(args[2])); + } + + private static void generate(File oldDir, File newDir, File out) throws IOException { + String oldVersionNumber = oldDir.getName(); + ConfigurationMetadataRepository oldMetadata = buildRepository(oldDir); + String newVersionNumber = newDir.getName(); + ConfigurationMetadataRepository newMetadata = buildRepository(newDir); + Changelog changelog = Changelog.of(oldVersionNumber, oldMetadata, newVersionNumber, newMetadata); + try (ChangelogWriter writer = new ChangelogWriter(out)) { + writer.write(changelog); + } + System.out.println("%nConfiguration metadata changelog written to '%s'".formatted(out)); + } + + static ConfigurationMetadataRepository buildRepository(File directory) { + ConfigurationMetadataRepositoryJsonBuilder builder = ConfigurationMetadataRepositoryJsonBuilder.create(); + for (File file : directory.listFiles()) { + try (JarFile jarFile = new JarFile(file)) { + JarEntry metadataEntry = jarFile.getJarEntry("META-INF/spring-configuration-metadata.json"); + if (metadataEntry != null) { + builder.withJsonResource(jarFile.getInputStream(metadataEntry)); + } + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + return builder.build(); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriter.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriter.java new file mode 100644 index 0000000000..fd79845d0e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriter.java @@ -0,0 +1,224 @@ +/* + * Copyright 2012-2023 the original author or 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 org.springframework.boot.configurationmetadata.changelog; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.text.BreakIterator; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; +import org.springframework.boot.configurationmetadata.Deprecation; + +/** + * Writes a {@link Changelog} using asciidoc markup. + * + * @author Stephane Nicoll + * @author Andy Wilkinson + * @author Phillip Webb + */ +class ChangelogWriter implements AutoCloseable { + + private static final Comparator 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> differencesByType = collateByType(changelog); + write("Configuration property changes between `%s` and `%s`%n", oldVersionNumber, newVersionNumber); + write("%n%n%n== Deprecated in %s%n", newVersionNumber); + writeDeprecated(differencesByType.get(DifferenceType.DEPRECATED)); + write("%n%n%n== Added in %s%n", newVersionNumber); + writeAdded(differencesByType.get(DifferenceType.ADDED)); + write("%n%n%n== Removed in %s%n", newVersionNumber); + writeRemoved(differencesByType.get(DifferenceType.DELETED), differencesByType.get(DifferenceType.DEPRECATED)); + } + + private Map> collateByType(Changelog differences) { + Map> 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 differences) { + List rows = sortProperties(differences, Difference::newProperty).stream() + .filter(this::isDeprecatedInRelease) + .collect(Collectors.toList()); + writeTable("| Key | Replacement | Reason", rows, this::writeDeprecated); + } + + private void writeDeprecated(Difference difference) { + writeDeprecatedPropertyRow(difference.newProperty()); + } + + private void writeAdded(List differences) { + List rows = sortProperties(differences, Difference::newProperty); + writeTable("| Key | Default value | Description", rows, this::writeAdded); + } + + private void writeAdded(Difference difference) { + writeRegularPropertyRow(difference.newProperty()); + } + + private void writeRemoved(List deleted, List deprecated) { + List rows = getRemoved(deleted, deprecated); + writeTable("| Key | Replacement | Reason", rows, this::writeRemoved); + } + + private List getRemoved(List deleted, List deprecated) { + List 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 sortProperties(List differences, + Function extractor) { + return differences.stream().sorted(Comparator.comparing(extractor, COMPARING_ID)).toList(); + } + + @SafeVarargs + @SuppressWarnings("varargs") + private P getFirstNonNull(T t, Function... extractors) { + return Stream.of(extractors) + .map((extractor) -> extractor.apply(t)) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } + + private void writeTable(String header, List rows, Consumer action) { + if (rows.isEmpty()) { + write("_None_.%n"); + } + else { + writeTableBreak(); + write(header + "%n%n"); + for (Iterator iterator = rows.iterator(); iterator.hasNext();) { + action.accept(iterator.next()); + write((!iterator.hasNext()) ? null : "%n"); + } + writeTableBreak(); + } + } + + private void writeTableBreak() { + write("|======================%n"); + } + + private void writeRegularPropertyRow(ConfigurationMetadataProperty property) { + writeCell(monospace(property.getId())); + writeCell(monospace(asString(property.getDefaultValue()))); + writeCell(property.getShortDescription()); + } + + 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 format, Object... args) { + write((format != null) ? "| %s%n".formatted(format) : "|%n", args); + } + + 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(); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogGenerator.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogGenerator.java deleted file mode 100644 index 2eb5b1244e..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogGenerator.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2012-2023 the original author or 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 org.springframework.boot.configurationmetadata.changelog; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; - -/** - * Generates a configuration metadata changelog. Requires three arguments: - * - *
    - *
  1. The path of a directory containing jar files from which the old metadata will be - * extracted - *
  2. The path of a directory containing jar files from which the new metadata will be - * extracted - *
  3. The path of a file to which the changelog will be written - *
- * - * The name of each directory will be used to name the old and new metadata in the - * generated changelog - * - * @author Andy Wilkinson - */ -final class ConfigurationMetadataChangelogGenerator { - - private ConfigurationMetadataChangelogGenerator() { - - } - - public static void main(String[] args) throws IOException { - ConfigurationMetadataDiff diff = ConfigurationMetadataDiff.of( - NamedConfigurationMetadataRepository.from(new File(args[0])), - NamedConfigurationMetadataRepository.from(new File(args[1]))); - try (ConfigurationMetadataChangelogWriter writer = new ConfigurationMetadataChangelogWriter( - new FileWriter(new File(args[2])))) { - writer.write(diff); - } - System.out.println("\nConfiguration metadata changelog written to '" + args[2] + "'"); - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogWriter.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogWriter.java deleted file mode 100644 index b08b1667d3..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogWriter.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2012-2023 the original author or 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 org.springframework.boot.configurationmetadata.changelog; - -import java.io.PrintWriter; -import java.io.Writer; -import java.text.BreakIterator; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; -import org.springframework.boot.configurationmetadata.Deprecation; -import org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataDiff.Difference; -import org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataDiff.Difference.Type; - -/** - * Writes a configuration metadata changelog from a {@link ConfigurationMetadataDiff}. - * - * @author Stephane Nicoll - * @author Andy Wilkinson - */ -class ConfigurationMetadataChangelogWriter implements AutoCloseable { - - private final PrintWriter out; - - ConfigurationMetadataChangelogWriter(Writer out) { - this.out = new PrintWriter(out); - } - - void write(ConfigurationMetadataDiff diff) { - this.out.append(String.format("Configuration property changes between `%s` and " + "`%s`%n", diff.leftName(), - diff.rightName())); - this.out.append(System.lineSeparator()); - this.out.append(String.format("== Deprecated in `%s`%n", diff.rightName())); - Map> differencesByType = differencesByType(diff); - writeDeprecatedProperties(differencesByType.get(Type.DEPRECATED)); - this.out.append(System.lineSeparator()); - this.out.append(String.format("== New in `%s`%n", diff.rightName())); - writeAddedProperties(differencesByType.get(Type.ADDED)); - this.out.append(System.lineSeparator()); - this.out.append(String.format("== Removed in `%s`%n", diff.rightName())); - writeRemovedProperties(differencesByType.get(Type.DELETED), differencesByType.get(Type.DEPRECATED)); - } - - private Map> differencesByType(ConfigurationMetadataDiff diff) { - Map> differencesByType = new HashMap<>(); - for (Type type : Type.values()) { - differencesByType.put(type, new ArrayList<>()); - } - for (Difference difference : diff.differences()) { - differencesByType.get(difference.type()).add(difference); - } - return differencesByType; - } - - private void writeDeprecatedProperties(List differences) { - if (differences.isEmpty()) { - this.out.append(String.format("None.%n")); - } - else { - List properties = sortProperties(differences, Difference::right).stream() - .filter(this::isDeprecatedInRelease) - .collect(Collectors.toList()); - this.out.append(String.format("|======================%n")); - this.out.append(String.format("|Key |Replacement |Reason%n")); - properties.forEach((diff) -> { - ConfigurationMetadataProperty property = diff.right(); - writeDeprecatedProperty(property); - }); - this.out.append(String.format("|======================%n")); - } - this.out.append(String.format("%n%n")); - } - - private boolean isDeprecatedInRelease(Difference difference) { - return difference.right().getDeprecation() != null - && Deprecation.Level.ERROR != difference.right().getDeprecation().getLevel(); - } - - private void writeAddedProperties(List differences) { - if (differences.isEmpty()) { - this.out.append(String.format("None.%n")); - } - else { - List properties = sortProperties(differences, Difference::right); - this.out.append(String.format("|======================%n")); - this.out.append(String.format("|Key |Default value |Description%n")); - properties.forEach((diff) -> writeRegularProperty(diff.right())); - this.out.append(String.format("|======================%n")); - } - this.out.append(String.format("%n%n")); - } - - private void writeRemovedProperties(List deleted, List deprecated) { - List removed = getRemovedProperties(deleted, deprecated); - if (removed.isEmpty()) { - this.out.append(String.format("None.%n")); - } - else { - this.out.append(String.format("|======================%n")); - this.out.append(String.format("|Key |Replacement |Reason%n")); - removed.forEach((property) -> writeDeprecatedProperty( - (property.right() != null) ? property.right() : property.left())); - this.out.append(String.format("|======================%n")); - } - } - - private List getRemovedProperties(List deleted, List deprecated) { - List properties = new ArrayList<>(deleted); - properties.addAll(deprecated.stream().filter((p) -> !isDeprecatedInRelease(p)).collect(Collectors.toList())); - return sortProperties(properties, - (difference) -> (difference.left() != null) ? difference.left() : difference.right()); - } - - private void writeRegularProperty(ConfigurationMetadataProperty property) { - this.out.append("|`").append(property.getId()).append("` |"); - if (property.getDefaultValue() != null) { - this.out.append("`").append(defaultValueToString(property.getDefaultValue())).append("`"); - } - this.out.append(" |"); - if (property.getDescription() != null) { - this.out.append(property.getShortDescription()); - } - this.out.append(System.lineSeparator()); - } - - private void writeDeprecatedProperty(ConfigurationMetadataProperty property) { - Deprecation deprecation = (property.getDeprecation() != null) ? property.getDeprecation() : new Deprecation(); - this.out.append("|`").append(property.getId()).append("` |"); - if (deprecation.getReplacement() != null) { - this.out.append("`").append(deprecation.getReplacement()).append("`"); - } - this.out.append(" |"); - if (deprecation.getReason() != null) { - this.out.append(getFirstSentence(deprecation.getReason())); - } - this.out.append(System.lineSeparator()); - } - - private String getFirstSentence(String text) { - 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); - } - else { - String[] lines = text.split(System.lineSeparator()); - return lines[0].trim(); - } - } - - private static String removeSpaceBetweenLine(String text) { - String[] lines = text.split(System.lineSeparator()); - StringBuilder sb = new StringBuilder(); - for (String line : lines) { - sb.append(line.trim()).append(" "); - } - return sb.toString().trim(); - } - - private List sortProperties(List properties, - Function property) { - List sorted = new ArrayList<>(properties); - sorted.sort((o1, o2) -> property.apply(o1).getId().compareTo(property.apply(o2).getId())); - return sorted; - } - - private static String defaultValueToString(Object defaultValue) { - if (defaultValue instanceof Object[]) { - return Stream.of((Object[]) defaultValue).map(Object::toString).collect(Collectors.joining(", ")); - } - else { - return defaultValue.toString(); - } - } - - @Override - public void close() { - this.out.close(); - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiff.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiff.java deleted file mode 100644 index 260c7f95ea..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiff.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2012-2023 the original author or 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 org.springframework.boot.configurationmetadata.changelog; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; -import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; -import org.springframework.boot.configurationmetadata.Deprecation.Level; -import org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataDiff.Difference.Type; - -/** - * A diff of two repositories of configuration metadata. - * - * @param leftName the name of the left-hand side of the diff - * @param rightName the name of the right-hand side of the diff - * @param differences the differences - * @author Stephane Nicoll - * @author Andy Wilkinson - */ -record ConfigurationMetadataDiff(String leftName, String rightName, List differences) { - - static ConfigurationMetadataDiff of(NamedConfigurationMetadataRepository left, - NamedConfigurationMetadataRepository right) { - return new ConfigurationMetadataDiff(left.getName(), right.getName(), differences(left, right)); - } - - private static List differences(ConfigurationMetadataRepository left, - ConfigurationMetadataRepository right) { - List differences = new ArrayList<>(); - List matches = new ArrayList<>(); - Map leftProperties = left.getAllProperties(); - Map rightProperties = right.getAllProperties(); - for (ConfigurationMetadataProperty leftProperty : leftProperties.values()) { - String id = leftProperty.getId(); - matches.add(id); - ConfigurationMetadataProperty rightProperty = rightProperties.get(id); - if (rightProperty == null) { - if (!(leftProperty.isDeprecated() && leftProperty.getDeprecation().getLevel() == Level.ERROR)) { - differences.add(new Difference(Type.DELETED, leftProperty, null)); - } - } - else if (rightProperty.isDeprecated() && !leftProperty.isDeprecated()) { - differences.add(new Difference(Type.DEPRECATED, leftProperty, rightProperty)); - } - else if (leftProperty.isDeprecated() && leftProperty.getDeprecation().getLevel() == Level.WARNING - && rightProperty.isDeprecated() && rightProperty.getDeprecation().getLevel() == Level.ERROR) { - differences.add(new Difference(Type.DELETED, leftProperty, rightProperty)); - } - } - for (ConfigurationMetadataProperty rightProperty : rightProperties.values()) { - if ((!matches.contains(rightProperty.getId())) && (!rightProperty.isDeprecated())) { - differences.add(new Difference(Type.ADDED, null, rightProperty)); - } - } - return differences; - } - - /** - * A difference in the metadata. - * - * @param type the type of the difference - * @param left the left-hand side of the difference - * @param right the right-hand side of the difference - */ - static record Difference(Type type, ConfigurationMetadataProperty left, ConfigurationMetadataProperty right) { - - /** - * The type of a difference in the metadata. - */ - enum Type { - - /** - * The entry has been added. - */ - ADDED, - - /** - * The entry has been made deprecated. It may or may not still exist in the - * previous version. - */ - DEPRECATED, - - /** - * The entry has been deleted. - */ - DELETED - - } - - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Difference.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Difference.java new file mode 100644 index 0000000000..8d0fb66cfa --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/Difference.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2023 the original author or 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 org.springframework.boot.configurationmetadata.changelog; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; +import org.springframework.boot.configurationmetadata.Deprecation.Level; + +/** + * A difference the metadata. + * + * @param type the type of the difference + * @param oldProperty the old property + * @param newProperty the new property + * @author Stephane Nicoll + * @author Andy Wilkinson + * @author Phillip Webb + */ +record Difference(DifferenceType type, ConfigurationMetadataProperty oldProperty, + ConfigurationMetadataProperty newProperty) { + + static Difference compute(ConfigurationMetadataProperty oldProperty, ConfigurationMetadataProperty newProperty) { + if (newProperty == null) { + if (!(oldProperty.isDeprecated() && oldProperty.getDeprecation().getLevel() == Level.ERROR)) { + return new Difference(DifferenceType.DELETED, oldProperty, null); + } + return null; + } + if (newProperty.isDeprecated() && !oldProperty.isDeprecated()) { + return new Difference(DifferenceType.DEPRECATED, oldProperty, newProperty); + } + if (oldProperty.isDeprecated() && oldProperty.getDeprecation().getLevel() == Level.WARNING + && newProperty.isDeprecated() && newProperty.getDeprecation().getLevel() == Level.ERROR) { + return new Difference(DifferenceType.DELETED, oldProperty, newProperty); + } + return null; + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/DifferenceType.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/DifferenceType.java new file mode 100644 index 0000000000..b673310b40 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/DifferenceType.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2023 the original author or 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 org.springframework.boot.configurationmetadata.changelog; + +/** + * The type of a difference in the metadata. + * + * @author Andy Wilkinson + */ +enum DifferenceType { + + /** + * The entry has been added. + */ + ADDED, + + /** + * The entry has been made deprecated. It may or may not still exist in the previous + * version. + */ + DEPRECATED, + + /** + * The entry has been deleted. + */ + DELETED + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/NamedConfigurationMetadataRepository.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/NamedConfigurationMetadataRepository.java deleted file mode 100644 index 51ec5535e3..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/NamedConfigurationMetadataRepository.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2012-2023 the original author or 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 org.springframework.boot.configurationmetadata.changelog; - -import java.io.File; -import java.io.IOException; -import java.util.Map; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -import org.springframework.boot.configurationmetadata.ConfigurationMetadataGroup; -import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; -import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; -import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder; - -/** - * A {@link ConfigurationMetadataRepository} with a name. - * - * @author Andy Wilkinson - */ -class NamedConfigurationMetadataRepository implements ConfigurationMetadataRepository { - - private final String name; - - private final ConfigurationMetadataRepository delegate; - - NamedConfigurationMetadataRepository(String name, ConfigurationMetadataRepository delegate) { - this.name = name; - this.delegate = delegate; - } - - /** - * The name of the metadata held in the repository. - * @return the name of the metadata - */ - String getName() { - return this.name; - } - - @Override - public Map getAllGroups() { - return this.delegate.getAllGroups(); - } - - @Override - public Map getAllProperties() { - return this.delegate.getAllProperties(); - } - - static NamedConfigurationMetadataRepository from(File metadataDir) { - ConfigurationMetadataRepositoryJsonBuilder builder = ConfigurationMetadataRepositoryJsonBuilder.create(); - for (File jar : metadataDir.listFiles()) { - try (JarFile jarFile = new JarFile(jar)) { - JarEntry jsonMetadata = jarFile.getJarEntry("META-INF/spring-configuration-metadata.json"); - if (jsonMetadata != null) { - builder.withJsonResource(jarFile.getInputStream(jsonMetadata)); - } - } - catch (IOException ex) { - throw new RuntimeException(ex); - } - } - return new NamedConfigurationMetadataRepository(metadataDir.getName(), builder.build()); - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGeneratorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGeneratorTests.java new file mode 100644 index 0000000000..efa1760c27 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogGeneratorTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2023 the original author or 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 org.springframework.boot.configurationmetadata.changelog; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipEntry; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ChangelogGenerator}. + * + * @author Phillip Webb + */ +class ChangelogGeneratorTests { + + @TempDir + File temp; + + @Test + void generateChangeLog() throws IOException { + File oldJars = new File(this.temp, "1.0"); + addJar(oldJars, "sample-1.0.json"); + File newJars = new File(this.temp, "2.0"); + addJar(newJars, "sample-2.0.json"); + File out = new File(this.temp, "changes.adoc"); + String[] args = new String[] { oldJars.getAbsolutePath(), newJars.getAbsolutePath(), out.getAbsolutePath() }; + ChangelogGenerator.main(args); + assertThat(out).usingCharset(StandardCharsets.UTF_8) + .hasSameTextualContentAs(new File("src/test/resources/sample.adoc")); + } + + private void addJar(File directory, String filename) throws IOException { + directory.mkdirs(); + try (JarOutputStream out = new JarOutputStream(new FileOutputStream(new File(directory, "sample.jar")))) { + out.putNextEntry(new ZipEntry("META-INF/spring-configuration-metadata.json")); + try (InputStream in = new FileInputStream("src/test/resources/" + filename)) { + in.transferTo(out); + out.closeEntry(); + } + } + + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogTests.java new file mode 100644 index 0000000000..8e8516e522 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012-2023 the original author or 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 org.springframework.boot.configurationmetadata.changelog; + +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link Changelog}. + * + * @author Stephane Nicoll + * @author Andy Wilkinson + */ +class ChangelogTests { + + @Test + void diffContainsDifferencesBetweenLeftAndRightInputs() { + Changelog differences = TestChangelog.load(); + assertThat(differences).isNotNull(); + assertThat(differences.oldVersionNumber()).isEqualTo("1.0"); + assertThat(differences.newVersionNumber()).isEqualTo("2.0"); + assertThat(differences.differences()).hasSize(4); + List added = differences.differences() + .stream() + .filter((difference) -> difference.type() == DifferenceType.ADDED) + .collect(Collectors.toList()); + assertThat(added).hasSize(1); + assertProperty(added.get(0).newProperty(), "test.add", String.class, "new"); + List deleted = differences.differences() + .stream() + .filter((difference) -> difference.type() == DifferenceType.DELETED) + .collect(Collectors.toList()); + assertThat(deleted).hasSize(2) + .anySatisfy((entry) -> assertProperty(entry.oldProperty(), "test.delete", String.class, "delete")) + .anySatisfy( + (entry) -> assertProperty(entry.newProperty(), "test.delete.deprecated", String.class, "delete")); + List deprecated = differences.differences() + .stream() + .filter((difference) -> difference.type() == DifferenceType.DEPRECATED) + .collect(Collectors.toList()); + assertThat(deprecated).hasSize(1); + assertProperty(deprecated.get(0).oldProperty(), "test.deprecate", String.class, "wrong"); + assertProperty(deprecated.get(0).newProperty(), "test.deprecate", String.class, "wrong"); + } + + private void assertProperty(ConfigurationMetadataProperty property, String id, Class type, Object defaultValue) { + assertThat(property).isNotNull(); + assertThat(property.getId()).isEqualTo(id); + assertThat(property.getType()).isEqualTo(type.getName()); + assertThat(property.getDefaultValue()).isEqualTo(defaultValue); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriterTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriterTests.java new file mode 100644 index 0000000000..5e72e3b567 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ChangelogWriterTests.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012-2023 the original author or 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 org.springframework.boot.configurationmetadata.changelog; + +import java.io.File; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; + +import org.assertj.core.util.Files; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ChangelogWriter}. + * + * @author Phillip Webb + */ +class ChangelogWriterTests { + + @Test + void writeChangelog() { + StringWriter out = new StringWriter(); + try (ChangelogWriter writer = new ChangelogWriter(out)) { + writer.write(TestChangelog.load()); + } + String expected = Files.contentOf(new File("src/test/resources/sample.adoc"), StandardCharsets.UTF_8); + assertThat(out).hasToString(expected); + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiffTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiffTests.java deleted file mode 100644 index 787184a93f..0000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiffTests.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2012-2023 the original author or 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 org.springframework.boot.configurationmetadata.changelog; - -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; -import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; -import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder; -import org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataDiff.Difference; -import org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataDiff.Difference.Type; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ConfigurationMetadataDiff}. - * - * @author Stephane Nicoll - * @author Andy Wilkinson - */ -class ConfigurationMetadataDiffTests { - - @Test - void diffContainsDifferencesBetweenLeftAndRightInputs() { - NamedConfigurationMetadataRepository left = new NamedConfigurationMetadataRepository("1.0", - load("sample-1.0.json")); - NamedConfigurationMetadataRepository right = new NamedConfigurationMetadataRepository("2.0", - load("sample-2.0.json")); - ConfigurationMetadataDiff diff = ConfigurationMetadataDiff.of(left, right); - assertThat(diff).isNotNull(); - assertThat(diff.leftName()).isEqualTo("1.0"); - assertThat(diff.rightName()).isEqualTo("2.0"); - assertThat(diff.differences()).hasSize(4); - List added = diff.differences() - .stream() - .filter((difference) -> difference.type() == Type.ADDED) - .collect(Collectors.toList()); - assertThat(added).hasSize(1); - assertProperty(added.get(0).right(), "test.add", String.class, "new"); - List deleted = diff.differences() - .stream() - .filter((difference) -> difference.type() == Type.DELETED) - .collect(Collectors.toList()); - assertThat(deleted).hasSize(2) - .anySatisfy((entry) -> assertProperty(entry.left(), "test.delete", String.class, "delete")) - .anySatisfy((entry) -> assertProperty(entry.right(), "test.delete.deprecated", String.class, "delete")); - List deprecated = diff.differences() - .stream() - .filter((difference) -> difference.type() == Type.DEPRECATED) - .collect(Collectors.toList()); - assertThat(deprecated).hasSize(1); - assertProperty(deprecated.get(0).left(), "test.deprecate", String.class, "wrong"); - assertProperty(deprecated.get(0).right(), "test.deprecate", String.class, "wrong"); - } - - private void assertProperty(ConfigurationMetadataProperty property, String id, Class type, Object defaultValue) { - assertThat(property).isNotNull(); - assertThat(property.getId()).isEqualTo(id); - assertThat(property.getType()).isEqualTo(type.getName()); - assertThat(property.getDefaultValue()).isEqualTo(defaultValue); - } - - private ConfigurationMetadataRepository load(String filename) { - try (InputStream inputStream = new FileInputStream("src/test/resources/" + filename)) { - return ConfigurationMetadataRepositoryJsonBuilder.create(inputStream).build(); - } - catch (IOException ex) { - throw new RuntimeException(ex); - } - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/TestChangelog.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/TestChangelog.java new file mode 100644 index 0000000000..58a1c34642 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/TestChangelog.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012-2023 the original author or 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 org.springframework.boot.configurationmetadata.changelog; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder; + +/** + * Factory to create test {@link Changelog} instance. + * + * @author Andy Wilkinson + * @author Phillip Webb + */ +final class TestChangelog { + + private TestChangelog() { + } + + static Changelog load() { + ConfigurationMetadataRepository previousRepository = load("sample-1.0.json"); + ConfigurationMetadataRepository repository = load("sample-2.0.json"); + return Changelog.of("1.0", previousRepository, "2.0", repository); + } + + private static ConfigurationMetadataRepository load(String filename) { + try (InputStream inputStream = new FileInputStream("src/test/resources/" + filename)) { + return ConfigurationMetadataRepositoryJsonBuilder.create(inputStream).build(); + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-2.0.json b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-2.0.json index 2de71ca99e..ef959d39c9 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-2.0.json +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-2.0.json @@ -27,7 +27,9 @@ "description": "Test delete deprecated.", "defaultValue": "delete", "deprecation": { - "level": "error" + "level": "error", + "replacement": "test.add", + "reason": "it was just bad" } } ] diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample.adoc b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample.adoc new file mode 100644 index 0000000000..ac5cc843e1 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample.adoc @@ -0,0 +1,32 @@ +Configuration property changes between `1.0` and `2.0` + + + +== Deprecated in 2.0 +_None_. + + + +== Added in 2.0 +|====================== +| Key | Default value | Description + +| `test.add` +| `new` +| Test add. +|====================== + + + +== Removed in 2.0 +|====================== +| Key | Replacement | Reason + +| `test.delete` +| +| + +| `test.delete.deprecated` +| `test.add` +| it was just bad +|======================