diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/MutableProjectDescription.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/MutableProjectDescription.java index fa37ec95..dc435e70 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/project/MutableProjectDescription.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/MutableProjectDescription.java @@ -61,6 +61,33 @@ public class MutableProjectDescription implements ProjectDescription { private String baseDirectory; + public MutableProjectDescription() { + super(); + } + + protected MutableProjectDescription(final MutableProjectDescription source) { + super(); + setPlatformVersion(source.getPlatformVersion()); + setBuildSystem(source.getBuildSystem()); + setPackaging(source.getPackaging()); + setLanguage(source.getLanguage()); + source.getRequestedDependencies().forEach(this::addDependency); + setGroupId(source.getGroupId()); + setArtifactId(source.getArtifactId()); + setVersion(source.getVersion()); + setName(source.getName()); + setDescription(source.getDescription()); + setApplicationName(source.getApplicationName()); + setPackageName(source.getPackageName()); + setBaseDirectory(source.getBaseDirectory()); + } + + @Override + public MutableProjectDescription createCopy() { + return new MutableProjectDescription(this); + } + + @Override public Version getPlatformVersion() { return this.platformVersion; } @@ -69,6 +96,7 @@ public class MutableProjectDescription implements ProjectDescription { this.platformVersion = platformVersion; } + @Override public BuildSystem getBuildSystem() { return this.buildSystem; } @@ -77,6 +105,7 @@ public class MutableProjectDescription implements ProjectDescription { this.buildSystem = buildSystem; } + @Override public Packaging getPackaging() { return this.packaging; } @@ -85,6 +114,7 @@ public class MutableProjectDescription implements ProjectDescription { this.packaging = packaging; } + @Override public Language getLanguage() { return this.language; } @@ -101,10 +131,12 @@ public class MutableProjectDescription implements ProjectDescription { return addDependency(id, builder.build()); } + @Override public Map getRequestedDependencies() { return Collections.unmodifiableMap(this.requestedDependencies); } + @Override public String getGroupId() { return this.groupId; } @@ -113,6 +145,7 @@ public class MutableProjectDescription implements ProjectDescription { this.groupId = groupId; } + @Override public String getArtifactId() { return this.artifactId; } @@ -121,6 +154,7 @@ public class MutableProjectDescription implements ProjectDescription { this.artifactId = artifactId; } + @Override public String getVersion() { return this.version; } @@ -129,6 +163,7 @@ public class MutableProjectDescription implements ProjectDescription { this.version = version; } + @Override public String getName() { return this.name; } @@ -137,6 +172,7 @@ public class MutableProjectDescription implements ProjectDescription { this.name = name; } + @Override public String getDescription() { return this.description; } @@ -145,6 +181,7 @@ public class MutableProjectDescription implements ProjectDescription { this.description = description; } + @Override public String getApplicationName() { return this.applicationName; } @@ -153,6 +190,7 @@ public class MutableProjectDescription implements ProjectDescription { this.applicationName = applicationName; } + @Override public String getPackageName() { if (StringUtils.hasText(this.packageName)) { return this.packageName; @@ -167,6 +205,7 @@ public class MutableProjectDescription implements ProjectDescription { this.packageName = packageName; } + @Override public String getBaseDirectory() { return this.baseDirectory; } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectDescription.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectDescription.java index fca2bdbe..28dcc5d7 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectDescription.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectDescription.java @@ -110,4 +110,15 @@ public interface ProjectDescription { */ String getBaseDirectory(); + /** + * ProjectDescription implementations should implement this to create a copy of + * themselves. However, the default implementation throws + * UnsupportedOperationException. + * @return never + * @throws UnsupportedOperationException always + */ + default ProjectDescription createCopy() { + throw new UnsupportedOperationException(); + } + } diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerator.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerator.java index 85d97c49..efaa8c83 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerator.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerator.java @@ -21,6 +21,10 @@ import java.util.List; import java.util.function.Consumer; import java.util.function.Supplier; +import io.spring.initializr.generator.project.diff.DefaultProjectDescriptionDiffFactory; +import io.spring.initializr.generator.project.diff.ProjectDescriptionDiff; +import io.spring.initializr.generator.project.diff.ProjectDescriptionDiffFactory; + import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.ImportSelector; @@ -119,9 +123,20 @@ public class ProjectGenerator { private Supplier resolve(ProjectDescription description, ProjectGenerationContext context) { return () -> { + if (description instanceof MutableProjectDescription) { + + final MutableProjectDescription mutableProjectDesc = (MutableProjectDescription) description; + + // Find the diff factory and use it to create and register a diff bean + final ProjectDescriptionDiffFactory diffFactory = context + .getBeanProvider(ProjectDescriptionDiffFactory.class) + .getIfAvailable(() -> new DefaultProjectDescriptionDiffFactory()); + final ProjectDescriptionDiff diff = diffFactory.create(mutableProjectDesc); + context.registerBean(ProjectDescriptionDiff.class, () -> diff); + context.getBeanProvider(ProjectDescriptionCustomizer.class).orderedStream() - .forEach((customizer) -> customizer.customize((MutableProjectDescription) description)); + .forEach((customizer) -> customizer.customize(mutableProjectDesc)); } return description; }; diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/diff/DefaultProjectDescriptionDiffFactory.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/diff/DefaultProjectDescriptionDiffFactory.java new file mode 100644 index 00000000..717ce260 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/diff/DefaultProjectDescriptionDiffFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-2019 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 io.spring.initializr.generator.project.diff; + +import io.spring.initializr.generator.project.ProjectDescription; + +/** + * A default {@link ProjectDescriptionDiffFactory} implementation that creates default + * {@link ProjectDescriptionDiff} instances. + * + * @author Chris Bono + */ +public class DefaultProjectDescriptionDiffFactory implements ProjectDescriptionDiffFactory { + + @Override + public ProjectDescriptionDiff create(final ProjectDescription description) { + return new ProjectDescriptionDiff(description); + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/diff/ProjectDescriptionDiff.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/diff/ProjectDescriptionDiff.java new file mode 100644 index 00000000..7ab832e5 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/diff/ProjectDescriptionDiff.java @@ -0,0 +1,233 @@ +/* + * Copyright 2012-2019 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 io.spring.initializr.generator.project.diff; + +import java.lang.reflect.Field; +import java.util.Objects; +import java.util.function.BiConsumer; + +import io.spring.initializr.generator.buildsystem.BuildSystem; +import io.spring.initializr.generator.language.Language; +import io.spring.initializr.generator.packaging.Packaging; +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.version.Version; + +import org.springframework.util.ReflectionUtils; + +/** + * Provides a convenient API for determining if certain fields on a + * {@link ProjectDescription} were modified. + * + * @author Chris Bono + */ +public class ProjectDescriptionDiff { + + private final ProjectDescription original; + + /** + * Construct a {@link ProjectDescriptionDiff} that creates and uses a copy of the + * specified description as its source. + * @param original the description to copy as the source + */ + public ProjectDescriptionDiff(final ProjectDescription original) { + this.original = original.createCopy(); + } + + /** + * Gets the copy of the originally specified description that is being tracked. + * @return copy of the originally specified description + */ + public ProjectDescription getOriginal() { + return this.original; + } + + /** + * Calls the specified consumer if the {@code platformVersion} is different on the + * original source project description than the specified project description. + * @param current the project description to test against + * @param consumer to call if the property has changed + */ + public void ifPlatformVersionChanged(final ProjectDescription current, + final BiConsumer consumer) { + if (!Objects.equals(this.original.getPlatformVersion(), current.getPlatformVersion())) { + consumer.accept(this.original.getPlatformVersion(), current.getPlatformVersion()); + } + } + + /** + * Calls the specified consumer if the {@code buildSystem} is different on the + * original source project description than the specified project description. + * @param current the description to test against + * @param consumer to call if the property has changed + */ + public void ifBuildSystemChanged(final ProjectDescription current, + final BiConsumer consumer) { + if (!Objects.equals(this.original.getBuildSystem(), current.getBuildSystem())) { + consumer.accept(this.original.getBuildSystem(), current.getBuildSystem()); + } + } + + /** + * Calls the specified consumer if the {@code packaging} is different on the original + * source project description than the specified project description. + * @param current the description to test against + * @param consumer to call if the property has changed + */ + public void ifPackagingChanged(final ProjectDescription current, final BiConsumer consumer) { + if (!Objects.equals(this.original.getPackaging(), current.getPackaging())) { + consumer.accept(this.original.getPackaging(), current.getPackaging()); + } + } + + /** + * Calls the specified consumer if the {@code language} is different on the original + * source project description than the specified project description. + * @param current the description to test against + * @param consumer to call if the property has changed + */ + public void ifLanguageChanged(final ProjectDescription current, final BiConsumer consumer) { + if (!Objects.equals(this.original.getLanguage(), current.getLanguage())) { + consumer.accept(this.original.getLanguage(), current.getLanguage()); + } + } + + /** + * Calls the specified consumer if the {@code groupId} is different on the original + * source project description than the specified project description. + * @param current the description to test against + * @param consumer to call if the property has changed + */ + public void ifGroupIdChanged(final ProjectDescription current, final BiConsumer consumer) { + if (!Objects.equals(this.original.getGroupId(), current.getGroupId())) { + consumer.accept(this.original.getGroupId(), current.getGroupId()); + } + } + + /** + * Calls the specified consumer if the {@code artifactId} is different on the original + * source project description than the specified project description. + * @param current the description to test against + * @param consumer to call if the property has changed + */ + public void ifArtifactIdChanged(final ProjectDescription current, final BiConsumer consumer) { + if (!Objects.equals(this.original.getArtifactId(), current.getArtifactId())) { + consumer.accept(this.original.getArtifactId(), current.getArtifactId()); + } + } + + /** + * Calls the specified consumer if the {@code version} is different on the original + * source project description than the specified project description. + * @param current the description to test against + * @param consumer to call if the property has changed + */ + public void ifVersionChanged(final ProjectDescription current, final BiConsumer consumer) { + if (!Objects.equals(this.original.getVersion(), current.getVersion())) { + consumer.accept(this.original.getVersion(), current.getVersion()); + } + } + + /** + * Calls the specified consumer if the {@code name} is different on the original + * source project description than the specified project description. + * @param current the description to test against + * @param consumer to call if the property has changed + */ + public void ifNameChanged(final ProjectDescription current, final BiConsumer consumer) { + if (!Objects.equals(this.original.getName(), current.getName())) { + consumer.accept(this.original.getName(), current.getName()); + } + } + + /** + * Calls the specified consumer if the {@code description} is different on the + * original source project description than the specified project description. + * @param current the description to test against + * @param consumer to call if the property has changed + */ + public void ifDescriptionChanged(final ProjectDescription current, final BiConsumer consumer) { + if (!Objects.equals(this.original.getDescription(), current.getDescription())) { + consumer.accept(this.original.getDescription(), current.getDescription()); + } + } + + /** + * Calls the specified consumer if the {@code applicationName} is different on the + * original source project description than the specified project description. + * @param current the description to test against + * @param consumer to call if the property has changed + */ + public void ifApplicationNameChanged(final ProjectDescription current, final BiConsumer consumer) { + if (!Objects.equals(this.original.getApplicationName(), current.getApplicationName())) { + consumer.accept(this.original.getApplicationName(), current.getApplicationName()); + } + } + + /** + * Calls the specified consumer if the {@code packageName} is different on the + * original source project description than the specified project description. + * @param current the description to test against + * @param consumer to call if the property has changed + */ + public void ifPackageNameChanged(final ProjectDescription current, final BiConsumer consumer) { + if (!Objects.equals(this.original.getPackageName(), current.getPackageName())) { + consumer.accept(this.original.getPackageName(), current.getPackageName()); + } + } + + /** + * Calls the specified consumer if the {@code baseDirectory} is different on the + * original source project description than the specified project description. + * @param current the description to test against + * @param consumer to call if the property has changed + */ + public void ifBaseDirectoryChanged(final ProjectDescription current, final BiConsumer consumer) { + if (!Objects.equals(this.original.getBaseDirectory(), current.getBaseDirectory())) { + consumer.accept(this.original.getBaseDirectory(), current.getBaseDirectory()); + } + } + + /** + * Calls the specified consumer if the value of the specified property is different on + * the original source project description than the specified project description. + * @param current the description to test against + * @param property the name of the property to check + * @param propertyClass the class of the property to check + * @param consumer to call if the property has changed + * @param type of the property + */ + public void ifPropertyChanged(final ProjectDescription current, final String property, + final Class propertyClass, final BiConsumer consumer) { + final V originalValue = getPropertyValueReflectively(this.original, property); + final V currentValue = getPropertyValueReflectively(current, property); + if (!Objects.equals(originalValue, currentValue)) { + consumer.accept(originalValue, currentValue); + } + } + + private V getPropertyValueReflectively(final ProjectDescription description, final String property) { + final Class descriptionClass = description.getClass(); + final Field field = ReflectionUtils.findField(descriptionClass, property); + if (field == null) { + throw new IllegalArgumentException( + String.format("No property named '%s' in '%s'.", property, descriptionClass.getSimpleName())); + } + ReflectionUtils.makeAccessible(field); + return (V) ReflectionUtils.getField(field, description); + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/diff/ProjectDescriptionDiffFactory.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/diff/ProjectDescriptionDiffFactory.java new file mode 100644 index 00000000..eda62aa9 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/diff/ProjectDescriptionDiffFactory.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2019 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 io.spring.initializr.generator.project.diff; + +import io.spring.initializr.generator.project.ProjectDescription; + +/** + * A factory for {@link ProjectDescriptionDiff} objects. + * + * @param the type of {@link ProjectDescription} + * @author Chris Bono + */ +public interface ProjectDescriptionDiffFactory { + + /** + * Construct a diff for the specified {@link ProjectDescription}. + *

+ * @param description the project description to use as the source of the diff + * @return a diff instance using the current state of the specified description as its + * source + */ + ProjectDescriptionDiff create(T description); + +} diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/project/ProjectGeneratorTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/project/ProjectGeneratorTests.java index 8b68ae32..4d837230 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/project/ProjectGeneratorTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/project/ProjectGeneratorTests.java @@ -20,6 +20,9 @@ import java.io.IOException; import java.util.Map; import java.util.function.Consumer; +import io.spring.initializr.generator.project.diff.DefaultProjectDescriptionDiffFactory; +import io.spring.initializr.generator.project.diff.ProjectDescriptionDiff; +import io.spring.initializr.generator.project.diff.ProjectDescriptionDiffFactory; import org.junit.jupiter.api.Test; import org.mockito.InOrder; @@ -53,6 +56,17 @@ public class ProjectGeneratorTests { assertThat(description).isSameAs(beanDescription); } + @Test + void generateRegisterProjectDescriptionDiff() { + ProjectGenerator generator = new ProjectGenerator((context) -> context + .registerBean(ProjectDescriptionDiffFactory.class, () -> new DefaultProjectDescriptionDiffFactory())); + MutableProjectDescription description = new MutableProjectDescription(); + generator.generate(description, (context) -> { + assertThat(context.getBeansOfType(ProjectDescriptionDiff.class)).hasSize(1); + return context.getBean(ProjectDescriptionDiff.class); + }); + } + @Test void generateInvokeContextInitializerBeforeContextIsRefreshed() { ProjectGenerator generator = new ProjectGenerator((context) -> { diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/project/diff/ProjectDescriptionDiffTest.java b/initializr-generator/src/test/java/io/spring/initializr/generator/project/diff/ProjectDescriptionDiffTest.java new file mode 100644 index 00000000..c611117c --- /dev/null +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/project/diff/ProjectDescriptionDiffTest.java @@ -0,0 +1,283 @@ +/* + * Copyright 2012-2019 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 io.spring.initializr.generator.project.diff; + +import java.util.function.BiConsumer; + +import io.spring.initializr.generator.buildsystem.BuildSystem; +import io.spring.initializr.generator.buildsystem.gradle.GradleBuildSystem; +import io.spring.initializr.generator.buildsystem.maven.MavenBuildSystem; +import io.spring.initializr.generator.language.Language; +import io.spring.initializr.generator.language.java.JavaLanguage; +import io.spring.initializr.generator.packaging.Packaging; +import io.spring.initializr.generator.project.MutableProjectDescription; +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.version.Version; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +class ProjectDescriptionDiffTest { + + @Test + void originalIsCopied() { + ProjectDescription original = createProjectDescription(); + ProjectDescriptionDiff diff = new ProjectDescriptionDiff(original); + assertThat(diff.getOriginal()).usingRecursiveComparison().isEqualTo(original); + assertThat(diff.getOriginal()).isNotSameAs(original); + } + + @Test + void noChanges() { + MutableProjectDescription description = createProjectDescription(); + ProjectDescriptionDiff diff = new ProjectDescriptionDiff(description); + BiConsumer failIfCalled = (v1, v2) -> fail("Values should not have changed"); + diff.ifPlatformVersionChanged(description, failIfCalled); + diff.ifBuildSystemChanged(description, failIfCalled); + diff.ifPackagingChanged(description, failIfCalled); + diff.ifLanguageChanged(description, failIfCalled); + diff.ifGroupIdChanged(description, failIfCalled); + diff.ifArtifactIdChanged(description, failIfCalled); + diff.ifVersionChanged(description, failIfCalled); + diff.ifNameChanged(description, failIfCalled); + diff.ifDescriptionChanged(description, failIfCalled); + diff.ifApplicationNameChanged(description, failIfCalled); + diff.ifPackageNameChanged(description, failIfCalled); + diff.ifBaseDirectoryChanged(description, failIfCalled); + diff.ifPropertyChanged(description, "name", String.class, failIfCalled); + } + + @Test + void platformVersionChanged() { + MutableProjectDescription description = createProjectDescription(); + ProjectDescriptionDiff diff = new ProjectDescriptionDiff(description); + Version original = description.getPlatformVersion(); + description.setPlatformVersion(Version.parse("2.0.0.RELEASE")); + CallTrackingBiConsumer changeHandler = new CallTrackingBiConsumer<>((prev, curr) -> { + assertThat(prev).isEqualTo(original); + assertThat(curr).isEqualTo(description.getPlatformVersion()); + }); + diff.ifPlatformVersionChanged(description, changeHandler); + diff.ifPropertyChanged(description, "platformVersion", Version.class, changeHandler); + assertThat(changeHandler.timesCalled()).isEqualTo(2); + } + + @Test + void buildSystemChanged() { + MutableProjectDescription description = createProjectDescription(); + ProjectDescriptionDiff diff = new ProjectDescriptionDiff(description); + BuildSystem original = description.getBuildSystem(); + description.setBuildSystem(BuildSystem.forId(GradleBuildSystem.ID)); + CallTrackingBiConsumer changeHandler = new CallTrackingBiConsumer<>((prev, curr) -> { + assertThat(prev).isEqualTo(original); + assertThat(curr).isEqualTo(description.getBuildSystem()); + }); + diff.ifBuildSystemChanged(description, changeHandler); + diff.ifPropertyChanged(description, "buildSystem", BuildSystem.class, changeHandler); + assertThat(changeHandler.timesCalled()).isEqualTo(2); + } + + @Test + void packagingChanged() { + MutableProjectDescription description = createProjectDescription(); + ProjectDescriptionDiff diff = new ProjectDescriptionDiff(description); + Packaging original = description.getPackaging(); + description.setPackaging(Packaging.forId("war")); + CallTrackingBiConsumer changeHandler = new CallTrackingBiConsumer<>((prev, curr) -> { + assertThat(prev).isEqualTo(original); + assertThat(curr).isEqualTo(description.getPackaging()); + }); + diff.ifPackagingChanged(description, changeHandler); + diff.ifPropertyChanged(description, "packaging", Packaging.class, changeHandler); + assertThat(changeHandler.timesCalled()).isEqualTo(2); + } + + @Test + void languageChanged() { + MutableProjectDescription description = createProjectDescription(); + ProjectDescriptionDiff diff = new ProjectDescriptionDiff(description); + Language original = description.getLanguage(); + description.setLanguage(Language.forId(JavaLanguage.ID, "13")); + CallTrackingBiConsumer changeHandler = new CallTrackingBiConsumer<>((prev, curr) -> { + assertThat(prev).isEqualTo(original); + assertThat(curr).isEqualTo(description.getLanguage()); + }); + diff.ifLanguageChanged(description, changeHandler); + diff.ifPropertyChanged(description, "language", String.class, changeHandler); + assertThat(changeHandler.timesCalled()).isEqualTo(2); + } + + @Test + void groupIdChanged() { + MutableProjectDescription description = createProjectDescription(); + ProjectDescriptionDiff diff = new ProjectDescriptionDiff(description); + String original = description.getGroupId(); + description.setGroupId("group5150"); + CallTrackingBiConsumer changeHandler = new CallTrackingBiConsumer<>((prev, curr) -> { + assertThat(prev).isEqualTo(original); + assertThat(curr).isEqualTo(description.getGroupId()); + }); + diff.ifGroupIdChanged(description, changeHandler); + diff.ifPropertyChanged(description, "groupId", String.class, changeHandler); + assertThat(changeHandler.timesCalled()).isEqualTo(2); + } + + @Test + void artifactIdChanged() { + MutableProjectDescription description = createProjectDescription(); + ProjectDescriptionDiff diff = new ProjectDescriptionDiff(description); + String original = description.getArtifactId(); + description.setArtifactId("artifact5150"); + CallTrackingBiConsumer changeHandler = new CallTrackingBiConsumer<>((prev, curr) -> { + assertThat(prev).isEqualTo(original); + assertThat(curr).isEqualTo(description.getArtifactId()); + }); + diff.ifArtifactIdChanged(description, changeHandler); + diff.ifPropertyChanged(description, "artifactId", String.class, changeHandler); + assertThat(changeHandler.timesCalled()).isEqualTo(2); + } + + @Test + void versionChanged() { + MutableProjectDescription description = createProjectDescription(); + ProjectDescriptionDiff diff = new ProjectDescriptionDiff(description); + String original = description.getVersion(); + description.setVersion("version5150"); + CallTrackingBiConsumer changeHandler = new CallTrackingBiConsumer<>((prev, curr) -> { + assertThat(prev).isEqualTo(original); + assertThat(curr).isEqualTo(description.getVersion()); + }); + diff.ifVersionChanged(description, changeHandler); + diff.ifPropertyChanged(description, "version", String.class, changeHandler); + assertThat(changeHandler.timesCalled()).isEqualTo(2); + } + + @Test + void nameChanged() { + MutableProjectDescription description = createProjectDescription(); + ProjectDescriptionDiff diff = new ProjectDescriptionDiff(description); + String original = description.getName(); + description.setName("name5150"); + CallTrackingBiConsumer changeHandler = new CallTrackingBiConsumer<>((prev, curr) -> { + assertThat(prev).isEqualTo(original); + assertThat(curr).isEqualTo(description.getName()); + }); + diff.ifNameChanged(description, changeHandler); + diff.ifPropertyChanged(description, "name", String.class, changeHandler); + assertThat(changeHandler.timesCalled()).isEqualTo(2); + } + + @Test + void descriptionChanged() { + MutableProjectDescription description = createProjectDescription(); + ProjectDescriptionDiff diff = new ProjectDescriptionDiff(description); + String original = description.getDescription(); + description.setDescription("desc5150"); + CallTrackingBiConsumer changeHandler = new CallTrackingBiConsumer<>((prev, curr) -> { + assertThat(prev).isEqualTo(original); + assertThat(curr).isEqualTo(description.getDescription()); + }); + diff.ifDescriptionChanged(description, changeHandler); + diff.ifPropertyChanged(description, "description", String.class, changeHandler); + assertThat(changeHandler.timesCalled()).isEqualTo(2); + } + + @Test + void applicationNameChanged() { + MutableProjectDescription description = createProjectDescription(); + ProjectDescriptionDiff diff = new ProjectDescriptionDiff(description); + String original = description.getApplicationName(); + description.setApplicationName("appname5150"); + CallTrackingBiConsumer changeHandler = new CallTrackingBiConsumer<>((prev, curr) -> { + assertThat(prev).isEqualTo(original); + assertThat(curr).isEqualTo(description.getApplicationName()); + }); + diff.ifApplicationNameChanged(description, changeHandler); + diff.ifPropertyChanged(description, "applicationName", String.class, changeHandler); + assertThat(changeHandler.timesCalled()).isEqualTo(2); + } + + @Test + void packageNameChanged() { + MutableProjectDescription description = createProjectDescription(); + ProjectDescriptionDiff diff = new ProjectDescriptionDiff(description); + String original = description.getPackageName(); + description.setPackageName("pkg5150"); + CallTrackingBiConsumer changeHandler = new CallTrackingBiConsumer<>((prev, curr) -> { + assertThat(prev).isEqualTo(original); + assertThat(curr).isEqualTo(description.getPackageName()); + }); + diff.ifPackageNameChanged(description, changeHandler); + diff.ifPropertyChanged(description, "packageName", String.class, changeHandler); + assertThat(changeHandler.timesCalled()).isEqualTo(2); + } + + @Test + void baseDirectoryChanged() { + MutableProjectDescription description = createProjectDescription(); + ProjectDescriptionDiff diff = new ProjectDescriptionDiff(description); + String original = description.getBaseDirectory(); + description.setBaseDirectory("baseDir5150"); + CallTrackingBiConsumer changeHandler = new CallTrackingBiConsumer<>((prev, curr) -> { + assertThat(prev).isEqualTo(original); + assertThat(curr).isEqualTo(description.getBaseDirectory()); + }); + diff.ifBaseDirectoryChanged(description, changeHandler); + diff.ifPropertyChanged(description, "baseDirectory", String.class, changeHandler); + assertThat(changeHandler.timesCalled()).isEqualTo(2); + } + + private MutableProjectDescription createProjectDescription() { + MutableProjectDescription description = new MutableProjectDescription(); + description.setBuildSystem(BuildSystem.forId(MavenBuildSystem.ID)); + description.setLanguage(Language.forId(JavaLanguage.ID, "11")); + description.setPlatformVersion(Version.parse("2.2.0.RELEASE")); + description.setGroupId("com.example"); + description.setArtifactId("demo"); + description.setName("demo"); + description.setVersion("0.0.8"); + description.setApplicationName("DemoApplication"); + description.setPackageName("com.example.demo"); + description.setPackaging(Packaging.forId("jar")); + description.setBaseDirectory("."); + return description; + } + + static class CallTrackingBiConsumer implements BiConsumer { + + private final BiConsumer delegate; + + private int callCount; + + CallTrackingBiConsumer(BiConsumer delegate) { + this.delegate = delegate; + } + + @Override + public void accept(T t, U u) { + this.callCount++; + this.delegate.accept(t, u); + } + + int timesCalled() { + return this.callCount; + } + + } + +} diff --git a/initializr-web/src/main/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfiguration.java b/initializr-web/src/main/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfiguration.java index 079a6169..a08dcf4c 100644 --- a/initializr-web/src/main/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfiguration.java +++ b/initializr-web/src/main/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfiguration.java @@ -28,6 +28,8 @@ import io.spring.initializr.generator.io.SimpleIndentStrategy; import io.spring.initializr.generator.io.template.MustacheTemplateRenderer; import io.spring.initializr.generator.io.template.TemplateRenderer; import io.spring.initializr.generator.project.ProjectDirectoryFactory; +import io.spring.initializr.generator.project.diff.DefaultProjectDescriptionDiffFactory; +import io.spring.initializr.generator.project.diff.ProjectDescriptionDiffFactory; import io.spring.initializr.metadata.DependencyMetadataProvider; import io.spring.initializr.metadata.InitializrMetadata; import io.spring.initializr.metadata.InitializrMetadataBuilder; @@ -129,6 +131,12 @@ public class InitializrAutoConfiguration { return new DefaultDependencyMetadataProvider(); } + @Bean + @ConditionalOnMissingBean + ProjectDescriptionDiffFactory projectDescriptionDiffFactory() { + return new DefaultProjectDescriptionDiffFactory(); + } + /** * Initializr web configuration. */ diff --git a/initializr-web/src/test/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfigurationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfigurationTests.java index c5a36e81..3f39972f 100755 --- a/initializr-web/src/test/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfigurationTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfigurationTests.java @@ -17,6 +17,7 @@ package io.spring.initializr.web.autoconfigure; import io.spring.initializr.generator.io.template.TemplateRenderer; +import io.spring.initializr.generator.project.diff.ProjectDescriptionDiffFactory; import io.spring.initializr.metadata.DependencyMetadataProvider; import io.spring.initializr.metadata.InitializrMetadataProvider; import io.spring.initializr.web.controller.CommandLineMetadataController; @@ -63,6 +64,11 @@ class InitializrAutoConfigurationTests { this.contextRunner.run((context) -> assertThat(context).hasSingleBean(TemplateRenderer.class)); } + @Test + void autoConfigRegistersDiffFactory() { + this.contextRunner.run((context) -> assertThat(context).hasSingleBean(ProjectDescriptionDiffFactory.class)); + } + @Test void autoConfigWhenTemplateRendererBeanPresentDoesNotRegisterTemplateRenderer() { this.contextRunner.withUserConfiguration(CustomTemplateRendererConfiguration.class).run((context) -> { diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectContributor.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectContributor.java index cc135c95..5e3cca91 100644 --- a/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectContributor.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectContributor.java @@ -44,6 +44,7 @@ class CustomProjectContributor implements ProjectContributor { && ((CustomProjectDescription) this.description).isCustomFlag()) { Files.createFile(projectRoot.resolve("custom.txt")); } + } } diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectDescription.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectDescription.java index dc7fbaa2..c4cb8eef 100644 --- a/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectDescription.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectDescription.java @@ -28,6 +28,20 @@ class CustomProjectDescription extends MutableProjectDescription { private boolean customFlag; + CustomProjectDescription() { + super(); + } + + CustomProjectDescription(final CustomProjectDescription source) { + super(source); + setCustomFlag(source.isCustomFlag()); + } + + @Override + public CustomProjectDescription createCopy() { + return new CustomProjectDescription(this); + } + boolean isCustomFlag() { return this.customFlag; } diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectDescriptionDiff.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectDescriptionDiff.java new file mode 100644 index 00000000..16742bde --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectDescriptionDiff.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-2019 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 io.spring.initializr.web.controller.custom; + +import java.util.function.BiConsumer; + +import io.spring.initializr.generator.project.diff.ProjectDescriptionDiff; + +/** + * Extends the base {@link ProjectDescriptionDiff} to provide convenient diff methods on + * {@link CustomProjectDescription}. + * + * @author cbono + */ +class CustomProjectDescriptionDiff extends ProjectDescriptionDiff { + + CustomProjectDescriptionDiff(final CustomProjectDescription original) { + super(original); + } + + /** + * Optionally calls the specified consumer if the {@code customFlag} is different on + * the original source description and the specified current description. + * @param current the description to test against + * @param consumer to call if the property has changed + */ + void ifCUstomFlagChanged(final CustomProjectDescription current, final BiConsumer consumer) { + final CustomProjectDescription original = (CustomProjectDescription) super.getOriginal(); + if (original.isCustomFlag() != current.isCustomFlag()) { + consumer.accept(original.isCustomFlag(), current.isCustomFlag()); + } + } + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectDescriptionDiffFactory.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectDescriptionDiffFactory.java new file mode 100644 index 00000000..bd56bdd6 --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectDescriptionDiffFactory.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-2019 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 io.spring.initializr.web.controller.custom; + +import io.spring.initializr.generator.project.diff.ProjectDescriptionDiff; +import io.spring.initializr.generator.project.diff.ProjectDescriptionDiffFactory; + +/** + * A custom {@link ProjectDescriptionDiffFactory} implementation that creates custom + * {@link CustomProjectDescriptionDiff} instances. + * + * @author cbono + */ +public class CustomProjectDescriptionDiffFactory implements ProjectDescriptionDiffFactory { + + @Override + public ProjectDescriptionDiff create(final CustomProjectDescription description) { + return new CustomProjectDescriptionDiff(description); + } + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectDescriptionDiffTest.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectDescriptionDiffTest.java new file mode 100644 index 00000000..0085fa67 --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectDescriptionDiffTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012-2019 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 io.spring.initializr.web.controller.custom; + +import io.spring.initializr.generator.buildsystem.BuildSystem; +import io.spring.initializr.generator.buildsystem.maven.MavenBuildSystem; +import io.spring.initializr.generator.language.Language; +import io.spring.initializr.generator.language.java.JavaLanguage; +import io.spring.initializr.generator.packaging.Packaging; +import io.spring.initializr.generator.version.Version; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +/** + * Simple sanity test around the custom diff extension model. + */ +class CustomProjectDescriptionDiffTest { + + @Test + void sanityCheck() { + + CustomProjectDescription description = customProjectDescription(); + CustomProjectDescriptionDiffFactory diffFactory = new CustomProjectDescriptionDiffFactory(); + CustomProjectDescriptionDiff diff = (CustomProjectDescriptionDiff) diffFactory.create(description); + + // copied + assertThat(diff.getOriginal()).usingRecursiveComparison().isEqualTo(description); + assertThat(diff.getOriginal()).isNotSameAs(description); + + // no changes + diff.ifCUstomFlagChanged(description, (v1, v2) -> fail("Values should not have changed")); + + // changes + boolean originalValue = description.isCustomFlag(); + description.setCustomFlag(!originalValue); + + // TODO could use the CallTrackingBiConsumer that I used in initializr-generator + // tests but then where to put it? + final boolean[] called = { false }; + diff.ifCUstomFlagChanged(description, (prev, curr) -> { + assertThat(prev).isEqualTo(originalValue); + assertThat(curr).isEqualTo(description.isCustomFlag()); + called[0] = true; + }); + assertThat(called[0]).isTrue(); + } + + private CustomProjectDescription customProjectDescription() { + CustomProjectDescription description = new CustomProjectDescription(); + description.setBuildSystem(BuildSystem.forId(MavenBuildSystem.ID)); + description.setLanguage(Language.forId(JavaLanguage.ID, "11")); + description.setPlatformVersion(Version.parse("2.2.0.RELEASE")); + description.setGroupId("com.example"); + description.setArtifactId("demo"); + description.setName("demo"); + description.setVersion("0.0.8"); + description.setApplicationName("DemoApplication"); + description.setPackageName("com.example.demo"); + description.setPackaging(Packaging.forId("jar")); + description.setBaseDirectory("."); + description.setCustomFlag(true); + return description; + } + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/ProjectGenerationControllerCustomRequestIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/ProjectGenerationControllerCustomRequestIntegrationTests.java index 68c9b2c3..9144d89e 100644 --- a/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/ProjectGenerationControllerCustomRequestIntegrationTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/ProjectGenerationControllerCustomRequestIntegrationTests.java @@ -79,6 +79,11 @@ public class ProjectGenerationControllerCustomRequestIntegrationTests return new CustomProjectGenerationController(metadataProvider, projectGenerationInvoker); } + @Bean + CustomProjectDescriptionDiffFactory customProjectDescriptionDiffFactory() { + return new CustomProjectDescriptionDiffFactory(); + } + } static class CustomProjectRequestToDescriptionConverter