diff --git a/settings.gradle b/settings.gradle index 101c44479d..48b547ef1c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -53,6 +53,7 @@ include "spring-boot-project:spring-boot-tools:spring-boot-autoconfigure-process include "spring-boot-project:spring-boot-tools:spring-boot-buildpack-platform" include "spring-boot-project:spring-boot-tools:spring-boot-cli" include "spring-boot-project:spring-boot-tools:spring-boot-configuration-metadata" +include "spring-boot-project:spring-boot-tools:spring-boot-configuration-metadata-changelog-generator" include "spring-boot-project:spring-boot-tools:spring-boot-configuration-processor" include "spring-boot-project:spring-boot-tools:spring-boot-gradle-plugin" include "spring-boot-project:spring-boot-tools:spring-boot-gradle-test-support" 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 new file mode 100644 index 0000000000..4d5c517e90 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/build.gradle @@ -0,0 +1,58 @@ +plugins { + id "java" + id "org.springframework.boot.conventions" +} + +description = "Spring Boot Configuration Metadata Changelog Generator" + +configurations { + oldMetadata + newMetadata +} + +dependencies { + implementation(enforcedPlatform(project(":spring-boot-project:spring-boot-dependencies"))) + implementation(project(":spring-boot-project:spring-boot-tools:spring-boot-configuration-metadata")) + + testImplementation(enforcedPlatform(project(":spring-boot-project:spring-boot-dependencies"))) + testImplementation("org.assertj:assertj-core") + testImplementation("org.junit.jupiter:junit-jupiter") +} + +if (project.hasProperty("oldVersion") && project.hasProperty("newVersion")) { + dependencies { + ["spring-boot", + "spring-boot-actuator", + "spring-boot-actuator-autoconfigure", + "spring-boot-autoconfigure", + "spring-boot-devtools", + "spring-boot-test-autoconfigure"].each { + oldMetadata("org.springframework.boot:$it:$oldVersion") + newMetadata("org.springframework.boot:$it:$newVersion") + } + } + + def prepareOldMetadata = tasks.register("prepareOldMetadata", Sync) { + from(configurations.oldMetadata) + if (project.hasProperty("oldVersion")) { + destinationDir = project.file("build/configuration-metadata-diff/$oldVersion") + } + } + + def prepareNewMetadata = tasks.register("prepareNewMetadata", Sync) { + from(configurations.newMetadata) + if (project.hasProperty("newVersion")) { + destinationDir = project.file("build/configuration-metadata-diff/$newVersion") + } + } + + tasks.register("generate", JavaExec) { + inputs.files(prepareOldMetadata, prepareNewMetadata) + outputs.file(project.file("build/configuration-metadata-changelog.adoc")) + classpath = sourceSets.main.runtimeClasspath + mainClass = 'org.springframework.boot.configurationmetadata.changelog.ConfigurationMetadataChangelogGenerator' + 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/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 new file mode 100644 index 0000000000..2eb5b1244e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogGenerator.java @@ -0,0 +1,56 @@ +/* + * 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 new file mode 100644 index 0000000000..b08b1667d3 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataChangelogWriter.java @@ -0,0 +1,204 @@ +/* + * 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 new file mode 100644 index 0000000000..260c7f95ea --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiff.java @@ -0,0 +1,109 @@ +/* + * 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/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 new file mode 100644 index 0000000000..51ec5535e3 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/NamedConfigurationMetadataRepository.java @@ -0,0 +1,80 @@ +/* + * 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/main/java/org/springframework/boot/configurationmetadata/changelog/package-info.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/package-info.java new file mode 100644 index 0000000000..96eca3173f --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/main/java/org/springframework/boot/configurationmetadata/changelog/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Spring Boot configuration metadata changelog generator. + */ +package org.springframework.boot.configurationmetadata.changelog; 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 new file mode 100644 index 0000000000..787184a93f --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/java/org/springframework/boot/configurationmetadata/changelog/ConfigurationMetadataDiffTests.java @@ -0,0 +1,92 @@ +/* + * 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/resources/sample-1.0.json b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-1.0.json new file mode 100644 index 0000000000..a0584bc569 --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-1.0.json @@ -0,0 +1,31 @@ +{ + "properties": [ + { + "name": "test.equal", + "type": "java.lang.String", + "description": "Test equality.", + "defaultValue": "test" + }, + { + "name": "test.deprecate", + "type": "java.lang.String", + "description": "Test deprecate.", + "defaultValue": "wrong" + }, + { + "name": "test.delete", + "type": "java.lang.String", + "description": "Test delete.", + "defaultValue": "delete" + }, + { + "name": "test.delete.deprecated", + "type": "java.lang.String", + "description": "Test delete deprecated.", + "defaultValue": "delete", + "deprecation": { + "level": "warning" + } + } + ] +} \ No newline at end of file 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 new file mode 100644 index 0000000000..2de71ca99e --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-metadata-changelog-generator/src/test/resources/sample-2.0.json @@ -0,0 +1,34 @@ +{ + "properties": [ + { + "name": "test.add", + "type": "java.lang.String", + "description": "Test add.", + "defaultValue": "new" + }, + { + "name": "test.equal", + "type": "java.lang.String", + "description": "Test equality.", + "defaultValue": "test" + }, + { + "name": "test.deprecate", + "type": "java.lang.String", + "description": "Test deprecate.", + "defaultValue": "wrong", + "deprecation": { + "level": "error" + } + }, + { + "name": "test.delete.deprecated", + "type": "java.lang.String", + "description": "Test delete deprecated.", + "defaultValue": "delete", + "deprecation": { + "level": "error" + } + } + ] +} \ No newline at end of file diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml index 9062ad5e2b..3f7d8b5e06 100644 --- a/src/checkstyle/checkstyle-suppressions.xml +++ b/src/checkstyle/checkstyle-suppressions.xml @@ -70,6 +70,7 @@ +