From 0d5efd24baef8cbf0d97e87c5fb1d26bf9e1042b Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 7 Feb 2019 15:59:37 +0100 Subject: [PATCH] Add support for customizable project generation This commit adds a project generation infrastructure based on the abstraction defined thus far. Each project is described by a `ProjectDescription` that provides the basic information about the project such as language, build system, packaging, platform version and more. Each project runs in a dedicated `ProjectApplicationContext` where contributors and customizers are elected to generate the project. Customizers are meant to update the model based on the `ProjectDescription` and other factors while contributors consume models to generate project assets (build files, source files, etc). Because project generation runs in a dedicated context, components can be flagged with special conditions that enable them only when necessary. Several conditions are provided in this commit to enable a component based on the language, build system, packaging, platform version or requested dependency. See gh-340 Co-authored-by: Andy Wilkinson Co-authored-by: Madhura Bhave --- .../condition/ConditionalOnBuildSystem.java | 43 +++++ .../condition/ConditionalOnLanguage.java | 43 +++++ .../condition/ConditionalOnPackaging.java | 43 +++++ .../ConditionalOnPlatformVersion.java | 45 +++++ .../ConditionalOnRequestedDependency.java | 51 ++++++ .../condition/OnBuildSystemCondition.java | 43 +++++ .../condition/OnLanguageCondition.java | 46 +++++ .../condition/OnPackagingCondition.java | 46 +++++ .../condition/OnPlatformVersionCondition.java | 46 +++++ .../OnRequestedDependencyCondition.java | 41 +++++ .../condition/ProjectGenerationCondition.java | 42 +++++ .../generator/condition/package-info.java | 20 +++ .../project/DefaultProjectAssetGenerator.java | 64 +++++++ .../project/ProjectAssetGenerator.java | 38 ++++ .../generator/project/ProjectDescription.java | 164 ++++++++++++++++++ .../project/ProjectDescriptionCustomizer.java | 38 ++++ .../project/ProjectDirectoryFactory.java | 40 +++++ .../ProjectGenerationConfiguration.java | 38 ++++ .../project/ProjectGenerationContext.java | 28 +++ .../project/ProjectGenerationException.java | 35 ++++ .../generator/project/ProjectGenerator.java | 110 ++++++++++++ .../project/ResolvedProjectDescription.java | 138 +++++++++++++++ .../MultipleResourcesProjectContributor.java | 72 ++++++++ .../contributor/ProjectContributor.java | 46 +++++ .../SingleResourceProjectContributor.java | 60 +++++++ .../project/contributor/package-info.java | 20 +++ .../generator/project/package-info.java | 22 +++ .../ConditionalOnBuildSystemTests.java | 82 +++++++++ .../condition/ConditionalOnLanguageTests.java | 102 +++++++++++ .../ConditionalOnPackagingTests.java | 91 ++++++++++ .../ConditionalOnPlatformVersionTests.java | 108 ++++++++++++ ...ConditionalOnRequestedDependencyTests.java | 74 ++++++++ .../project/ProjectGeneratorTests.java | 134 ++++++++++++++ .../AbstractProjectGenerationTester.java | 143 +++++++++++++++ .../test/project/ProjectAssetTester.java | 118 +++++++++++++ .../test/project/ProjectGeneratorTester.java | 80 +++++++++ .../test/project/ProjectStructure.java | 85 +++++++++ 37 files changed, 2439 insertions(+) create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnBuildSystem.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnLanguage.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnPackaging.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnPlatformVersion.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnRequestedDependency.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnBuildSystemCondition.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnLanguageCondition.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnPackagingCondition.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnPlatformVersionCondition.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnRequestedDependencyCondition.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/condition/ProjectGenerationCondition.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/condition/package-info.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/project/DefaultProjectAssetGenerator.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectAssetGenerator.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectDescription.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectDescriptionCustomizer.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectDirectoryFactory.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerationConfiguration.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerationContext.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerationException.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerator.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/project/ResolvedProjectDescription.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/MultipleResourcesProjectContributor.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/ProjectContributor.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/SingleResourceProjectContributor.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/package-info.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/project/package-info.java create mode 100644 initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnBuildSystemTests.java create mode 100644 initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnLanguageTests.java create mode 100644 initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnPackagingTests.java create mode 100644 initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnPlatformVersionTests.java create mode 100644 initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnRequestedDependencyTests.java create mode 100644 initializr-generator/src/test/java/io/spring/initializr/generator/project/ProjectGeneratorTests.java create mode 100644 initializr-generator/src/test/java/io/spring/initializr/generator/test/project/AbstractProjectGenerationTester.java create mode 100644 initializr-generator/src/test/java/io/spring/initializr/generator/test/project/ProjectAssetTester.java create mode 100644 initializr-generator/src/test/java/io/spring/initializr/generator/test/project/ProjectGeneratorTester.java create mode 100644 initializr-generator/src/test/java/io/spring/initializr/generator/test/project/ProjectStructure.java diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnBuildSystem.java b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnBuildSystem.java new file mode 100644 index 00000000..8f7fd084 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnBuildSystem.java @@ -0,0 +1,43 @@ +/* + * 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 + * + * http://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.condition; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import io.spring.initializr.generator.buildsystem.BuildSystem; + +import org.springframework.context.annotation.Conditional; + +/** + * Condition that matches when a generated project will use a particular + * {@link BuildSystem}. + * + * @author Andy Wilkinson + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Documented +@Conditional(OnBuildSystemCondition.class) +public @interface ConditionalOnBuildSystem { + + String value(); + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnLanguage.java b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnLanguage.java new file mode 100644 index 00000000..d4036dd8 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnLanguage.java @@ -0,0 +1,43 @@ +/* + * 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 + * + * http://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.condition; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import io.spring.initializr.generator.language.Language; + +import org.springframework.context.annotation.Conditional; + +/** + * Condition that matches when a generated project will be written using a particular + * {@link Language}. + * + * @author Andy Wilkinson + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Documented +@Conditional(OnLanguageCondition.class) +public @interface ConditionalOnLanguage { + + String value(); + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnPackaging.java b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnPackaging.java new file mode 100644 index 00000000..4e73f962 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnPackaging.java @@ -0,0 +1,43 @@ +/* + * 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 + * + * http://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.condition; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import io.spring.initializr.generator.packaging.Packaging; + +import org.springframework.context.annotation.Conditional; + +/** + * Condition that matches when a generated project will use a particular + * {@link Packaging}. + * + * @author Andy Wilkinson + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Documented +@Conditional(OnPackagingCondition.class) +public @interface ConditionalOnPackaging { + + String value(); + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnPlatformVersion.java b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnPlatformVersion.java new file mode 100644 index 00000000..5c896e83 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnPlatformVersion.java @@ -0,0 +1,45 @@ +/* + * 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 + * + * http://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.condition; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Conditional; + +/** + * Condition that matches when a generated project is using a matching version of the + * platform. + * + * @author Andy Wilkinson + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Documented +@Conditional(OnPlatformVersionCondition.class) +public @interface ConditionalOnPlatformVersion { + + /** + * The version range to match. + * @return the version range + */ + String value(); + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnRequestedDependency.java b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnRequestedDependency.java new file mode 100644 index 00000000..3fd2ffc2 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnRequestedDependency.java @@ -0,0 +1,51 @@ +/* + * 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 + * + * http://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.condition; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import io.spring.initializr.generator.project.ResolvedProjectDescription; + +import org.springframework.context.annotation.Conditional; + +/** + * Condition that matches when a {@link ResolvedProjectDescription} defines a particular + * dependency. A generated project may ultimately define a different set of dependencies + * according to the contributors that have been executed. To contribute to the project + * according to the real set, prefer querying the model itself rather than using this + * condition. + * + * @author Andy Wilkinson + * @author Stephane Nicoll + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Documented +@Conditional(OnRequestedDependencyCondition.class) +public @interface ConditionalOnRequestedDependency { + + /** + * The identifier of the dependency. + * @return the dependency ID + */ + String value(); + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnBuildSystemCondition.java b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnBuildSystemCondition.java new file mode 100644 index 00000000..aa4e077b --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnBuildSystemCondition.java @@ -0,0 +1,43 @@ +/* + * 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 + * + * http://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.condition; + +import io.spring.initializr.generator.buildsystem.BuildSystem; +import io.spring.initializr.generator.project.ResolvedProjectDescription; + +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * {@link ProjectGenerationCondition Condition} implementation for + * {@link ConditionalOnBuildSystem}. + * + * @author Andy Wilkinson + */ +class OnBuildSystemCondition extends ProjectGenerationCondition { + + @Override + protected boolean matches(ResolvedProjectDescription projectDescription, + ConditionContext context, AnnotatedTypeMetadata metadata) { + String buildSystemId = (String) metadata + .getAllAnnotationAttributes(ConditionalOnBuildSystem.class.getName()) + .getFirst("value"); + BuildSystem buildSystem = BuildSystem.forId(buildSystemId); + return projectDescription.getBuildSystem().id().equals(buildSystem.id()); + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnLanguageCondition.java b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnLanguageCondition.java new file mode 100644 index 00000000..b8094e04 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnLanguageCondition.java @@ -0,0 +1,46 @@ +/* + * 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 + * + * http://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.condition; + +import io.spring.initializr.generator.language.Language; +import io.spring.initializr.generator.project.ResolvedProjectDescription; + +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * {@link ProjectGenerationCondition Condition} implementation for + * {@link ConditionalOnLanguage}. + * + * @author Andy Wilkinson + */ +class OnLanguageCondition extends ProjectGenerationCondition { + + @Override + protected boolean matches(ResolvedProjectDescription projectDescription, + ConditionContext context, AnnotatedTypeMetadata metadata) { + if (projectDescription.getLanguage() == null) { + return false; + } + String languageId = (String) metadata + .getAllAnnotationAttributes(ConditionalOnLanguage.class.getName()) + .getFirst("value"); + Language language = Language.forId(languageId, null); + return projectDescription.getLanguage().id().equals(language.id()); + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnPackagingCondition.java b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnPackagingCondition.java new file mode 100644 index 00000000..7d5a4369 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnPackagingCondition.java @@ -0,0 +1,46 @@ +/* + * 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 + * + * http://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.condition; + +import io.spring.initializr.generator.packaging.Packaging; +import io.spring.initializr.generator.project.ResolvedProjectDescription; + +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * {@link ProjectGenerationCondition Condition} implementation for + * {@link ConditionalOnPackaging}. + * + * @author Andy Wilkinson + */ +class OnPackagingCondition extends ProjectGenerationCondition { + + @Override + protected boolean matches(ResolvedProjectDescription projectDescription, + ConditionContext context, AnnotatedTypeMetadata metadata) { + if (projectDescription.getPackaging() == null) { + return false; + } + String packagingId = (String) metadata + .getAllAnnotationAttributes(ConditionalOnPackaging.class.getName()) + .getFirst("value"); + Packaging packaging = Packaging.forId(packagingId); + return projectDescription.getPackaging().id().equals(packaging.id()); + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnPlatformVersionCondition.java b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnPlatformVersionCondition.java new file mode 100644 index 00000000..1c597c9c --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnPlatformVersionCondition.java @@ -0,0 +1,46 @@ +/* + * 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 + * + * http://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.condition; + +import io.spring.initializr.generator.project.ResolvedProjectDescription; +import io.spring.initializr.generator.version.VersionParser; +import io.spring.initializr.generator.version.VersionRange; + +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * {@link ProjectGenerationCondition} implementation for + * {@link ConditionalOnPlatformVersion}. + * + * @author Andy Wilkinson + */ +class OnPlatformVersionCondition extends ProjectGenerationCondition { + + @Override + protected boolean matches(ResolvedProjectDescription projectDescription, + ConditionContext context, AnnotatedTypeMetadata metadata) { + if (projectDescription.getPlatformVersion() == null) { + return false; + } + VersionRange range = VersionParser.DEFAULT.parseRange((String) metadata + .getAnnotationAttributes(ConditionalOnPlatformVersion.class.getName()) + .get("value")); + return range.match(projectDescription.getPlatformVersion()); + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnRequestedDependencyCondition.java b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnRequestedDependencyCondition.java new file mode 100644 index 00000000..2ad81e11 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnRequestedDependencyCondition.java @@ -0,0 +1,41 @@ +/* + * 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 + * + * http://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.condition; + +import io.spring.initializr.generator.project.ResolvedProjectDescription; + +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * {@link ProjectGenerationCondition} implementation for + * {@link ConditionalOnRequestedDependency}. + * + * @author Andy Wilkinson + */ +class OnRequestedDependencyCondition extends ProjectGenerationCondition { + + @Override + protected boolean matches(ResolvedProjectDescription projectDescription, + ConditionContext context, AnnotatedTypeMetadata metadata) { + String id = (String) metadata + .getAnnotationAttributes(ConditionalOnRequestedDependency.class.getName()) + .get("value"); + return projectDescription.getRequestedDependencies().containsKey(id); + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ProjectGenerationCondition.java b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ProjectGenerationCondition.java new file mode 100644 index 00000000..3f6aea72 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ProjectGenerationCondition.java @@ -0,0 +1,42 @@ +/* + * 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 + * + * http://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.condition; + +import io.spring.initializr.generator.project.ResolvedProjectDescription; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * Base class for all project generation {@link Condition Conditions}. + * + * @author Andy Wilkinson + */ +public abstract class ProjectGenerationCondition implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + ResolvedProjectDescription projectDescription = context.getBeanFactory() + .getBean(ResolvedProjectDescription.class); + return matches(projectDescription, context, metadata); + } + + protected abstract boolean matches(ResolvedProjectDescription projectDescription, + ConditionContext context, AnnotatedTypeMetadata metadata); + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/condition/package-info.java b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/package-info.java new file mode 100644 index 00000000..3b45f52b --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/package-info.java @@ -0,0 +1,20 @@ +/* + * 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 + * + * http://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. + */ + +/** + * Project generation {@code Condition} annotations and supporting classes. + */ +package io.spring.initializr.generator.condition; diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/DefaultProjectAssetGenerator.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/DefaultProjectAssetGenerator.java new file mode 100644 index 00000000..cbd77bc8 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/DefaultProjectAssetGenerator.java @@ -0,0 +1,64 @@ +/* + * 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 + * + * http://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; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; + +import io.spring.initializr.generator.project.contributor.ProjectContributor; + +/** + * The default {@link ProjectAssetGenerator}. Generates a directory structure with all + * available {@link ProjectContributor project contributors}. + * + * @author Stephane Nicoll + */ +public class DefaultProjectAssetGenerator implements ProjectAssetGenerator { + + @Override + public Path generate(ProjectGenerationContext context) throws IOException { + ResolvedProjectDescription resolvedProjectDescription = context + .getBean(ResolvedProjectDescription.class); + Path projectRoot = context.getBean(ProjectDirectoryFactory.class) + .createProjectDirectory(resolvedProjectDescription); + Path projectDirectory = initializerProjectDirectory(projectRoot, + resolvedProjectDescription); + List contributors = context + .getBeanProvider(ProjectContributor.class).orderedStream() + .collect(Collectors.toList()); + for (ProjectContributor contributor : contributors) { + contributor.contribute(projectDirectory); + } + return projectRoot; + } + + private Path initializerProjectDirectory(Path rootDir, + ResolvedProjectDescription description) throws IOException { + if (description.getBaseDirectory() != null) { + Path dir = rootDir.resolve(description.getBaseDirectory()); + Files.createDirectories(dir); + return dir; + } + else { + return rootDir; + } + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectAssetGenerator.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectAssetGenerator.java new file mode 100644 index 00000000..c9f0644f --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectAssetGenerator.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 + * + * http://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; + +import java.io.IOException; + +/** + * Generate project assets using a {@link ProjectGenerationContext}. + * + * @param the type that gathers the project assets + * @author Stephane Nicoll + */ +@FunctionalInterface +public interface ProjectAssetGenerator { + + /** + * Generate project assets using the specified {@link ProjectGenerationContext}. + * @param context the context to use + * @return the type that gathers the project assets + * @throws IOException if writing project assets failed + */ + T generate(ProjectGenerationContext context) throws IOException; + +} 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 new file mode 100644 index 00000000..b53a256f --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectDescription.java @@ -0,0 +1,164 @@ +/* + * 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 + * + * http://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; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import io.spring.initializr.generator.buildsystem.BuildSystem; +import io.spring.initializr.generator.buildsystem.Dependency; +import io.spring.initializr.generator.language.Language; +import io.spring.initializr.generator.packaging.Packaging; +import io.spring.initializr.generator.version.Version; + +/** + * Description of a project to generate. + * + * @author Andy Wilkinson + */ +public class ProjectDescription { + + private Version platformVersion; + + private BuildSystem buildSystem; + + private Packaging packaging; + + private Language language; + + private final Map requestedDependencies = new LinkedHashMap<>(); + + private String groupId; + + private String artifactId; + + private String name; + + private String description; + + private String applicationName; + + private String packageName; + + private String baseDirectory; + + /** + * Resolve the state of this instance to a {@link ResolvedProjectDescription}. + * @return an immutable description. + */ + public ResolvedProjectDescription resolve() { + return new ResolvedProjectDescription(this); + } + + public Version getPlatformVersion() { + return this.platformVersion; + } + + public void setPlatformVersion(Version platformVersion) { + this.platformVersion = platformVersion; + } + + public BuildSystem getBuildSystem() { + return this.buildSystem; + } + + public void setBuildSystem(BuildSystem buildSystem) { + this.buildSystem = buildSystem; + } + + public Packaging getPackaging() { + return this.packaging; + } + + public void setPackaging(Packaging packaging) { + this.packaging = packaging; + } + + public Language getLanguage() { + return this.language; + } + + public void setLanguage(Language language) { + this.language = language; + } + + public Dependency addDependency(String id, Dependency dependency) { + return this.requestedDependencies.put(id, dependency); + } + + public Map getRequestedDependencies() { + return Collections.unmodifiableMap(this.requestedDependencies); + } + + public String getGroupId() { + return this.groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getArtifactId() { + return this.artifactId; + } + + public void setArtifactId(String artifactId) { + this.artifactId = artifactId; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getApplicationName() { + return this.applicationName; + } + + public void setApplicationName(String applicationName) { + this.applicationName = applicationName; + } + + public String getPackageName() { + return this.packageName; + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public String getBaseDirectory() { + return this.baseDirectory; + } + + public void setBaseDirectory(String baseDirectory) { + this.baseDirectory = baseDirectory; + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectDescriptionCustomizer.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectDescriptionCustomizer.java new file mode 100644 index 00000000..ad71ca7c --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectDescriptionCustomizer.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 + * + * http://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; + +import org.springframework.core.Ordered; + +/** + * Callback for customizing a {@link ProjectDescription}. Invoked with an {@link Ordered + * order} of {@code 0} by default, considering overriding {@link #getOrder()} to customize + * this behaviour. + * + * @author Stephane Nicoll + */ +@FunctionalInterface +public interface ProjectDescriptionCustomizer extends Ordered { + + void customize(ProjectDescription description); + + @Override + default int getOrder() { + return 0; + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectDirectoryFactory.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectDirectoryFactory.java new file mode 100644 index 00000000..811ff6ea --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectDirectoryFactory.java @@ -0,0 +1,40 @@ +/* + * 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 + * + * http://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; + +import java.io.IOException; +import java.nio.file.Path; + +/** + * A factory of project directory. + * + * @author Stephane Nicoll + */ +@FunctionalInterface +public interface ProjectDirectoryFactory { + + /** + * Create a dedicated project directory for the specified + * {@link ResolvedProjectDescription}. + * @param description the description of a project to generate + * @return a dedicated existing directory + * @throws IOException if creating the directory failed + */ + Path createProjectDirectory(ResolvedProjectDescription description) + throws IOException; + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerationConfiguration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerationConfiguration.java new file mode 100644 index 00000000..8ce729e2 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerationConfiguration.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 + * + * http://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; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Configuration; + +/** + * Specialization of {@link Configuration} for configuration of project generation. + * + * @author Andy Wilkinson + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Configuration +public @interface ProjectGenerationConfiguration { + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerationContext.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerationContext.java new file mode 100644 index 00000000..670f9892 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerationContext.java @@ -0,0 +1,28 @@ +/* + * 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 + * + * http://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; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +/** + * Provide configuration and infrastructure to generate a project. + * + * @author Stephane Nicoll + */ +public class ProjectGenerationContext extends AnnotationConfigApplicationContext { + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerationException.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerationException.java new file mode 100644 index 00000000..f18e7f9c --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerationException.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 + * + * http://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; + +/** + * Thrown when a project generation failure occurs. + * + * @author Stephane Nicoll + */ +@SuppressWarnings("serial") +public class ProjectGenerationException extends RuntimeException { + + public ProjectGenerationException(String message) { + super(message); + } + + public ProjectGenerationException(String message, Throwable cause) { + super(message, cause); + } + +} 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 new file mode 100644 index 00000000..5ca32a32 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectGenerator.java @@ -0,0 +1,110 @@ +/* + * 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 + * + * http://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; + +import java.io.IOException; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportSelector; +import org.springframework.core.io.support.SpringFactoriesLoader; +import org.springframework.core.type.AnnotationMetadata; + +/** + * Main entry point for project generation. + * + * @author Andy Wilkinson + * @author Stephane Nicoll + */ +public class ProjectGenerator { + + private final Consumer projectGenerationContext; + + /** + * Create an instance with a customizer for the project generator application context. + * @param projectGenerationContext a consumer of the project generation context before + * it is refreshed. + */ + public ProjectGenerator(Consumer projectGenerationContext) { + this.projectGenerationContext = projectGenerationContext; + } + + /** + * Generate project assets using the specified {@link ProjectAssetGenerator}. + * @param description the description of the project to generate + * @param projectAssetGenerator the {@link ProjectAssetGenerator} to invoke + * @param the type that gathers the project assets + * @return the generated content + * @throws ProjectGenerationException if an error occurs while generating the project + */ + public T generate(ProjectDescription description, + ProjectAssetGenerator projectAssetGenerator) + throws ProjectGenerationException { + try (ProjectGenerationContext context = new ProjectGenerationContext()) { + context.registerBean(ResolvedProjectDescription.class, + resolve(description, context)); + context.register(CoreConfiguration.class); + this.projectGenerationContext.accept(context); + context.refresh(); + try { + return projectAssetGenerator.generate(context); + } + catch (IOException ex) { + throw new ProjectGenerationException("Failed to generate project", ex); + } + } + } + + private Supplier resolve(ProjectDescription description, + ProjectGenerationContext context) { + return () -> { + context.getBeanProvider(ProjectDescriptionCustomizer.class).orderedStream() + .forEach((customizer) -> customizer.customize(description)); + return new ResolvedProjectDescription(description); + }; + } + + /** + * Configuration used to bootstrap the application context used for project + * generation. + */ + @Configuration + @Import(ProjectGenerationImportSelector.class) + static class CoreConfiguration { + + } + + /** + * {@link ImportSelector} for loading classes configured in {@code spring.factories} + * using the + * {@code io.spring.initializr.generator.project.ProjectGenerationConfiguration} key. + */ + static class ProjectGenerationImportSelector implements ImportSelector { + + @Override + public String[] selectImports(AnnotationMetadata importingClassMetadata) { + List factories = SpringFactoriesLoader.loadFactoryNames( + ProjectGenerationConfiguration.class, getClass().getClassLoader()); + return factories.toArray(new String[0]); + } + + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/ResolvedProjectDescription.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ResolvedProjectDescription.java new file mode 100644 index 00000000..bc40f482 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ResolvedProjectDescription.java @@ -0,0 +1,138 @@ +/* + * 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 + * + * http://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; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import io.spring.initializr.generator.buildsystem.BuildSystem; +import io.spring.initializr.generator.buildsystem.Dependency; +import io.spring.initializr.generator.language.Language; +import io.spring.initializr.generator.packaging.Packaging; +import io.spring.initializr.generator.version.Version; + +import org.springframework.util.StringUtils; + +/** + * An immutable description of a project that is being generated. + * + * @author Madhura Bhave + */ +public final class ResolvedProjectDescription { + + private final Map requestedDependencies; + + private final Version platformVersion; + + private final BuildSystem buildSystem; + + private final Packaging packaging; + + private final Language language; + + private final String groupId; + + private final String artifactId; + + private final String name; + + private final String description; + + private final String applicationName; + + private final String packageName; + + private final String baseDirectory; + + public ResolvedProjectDescription(ProjectDescription description) { + this.platformVersion = description.getPlatformVersion(); + this.buildSystem = description.getBuildSystem(); + this.packaging = description.getPackaging(); + this.language = description.getLanguage(); + this.groupId = description.getGroupId(); + this.artifactId = description.getArtifactId(); + this.name = description.getName(); + this.description = description.getDescription(); + this.applicationName = description.getApplicationName(); + this.packageName = getPackageName(description); + this.baseDirectory = description.getBaseDirectory(); + Map requestedDependencies = new LinkedHashMap<>( + description.getRequestedDependencies()); + this.requestedDependencies = Collections.unmodifiableMap(requestedDependencies); + } + + private String getPackageName(ProjectDescription description) { + if (StringUtils.hasText(description.getPackageName())) { + return description.getPackageName(); + } + if (StringUtils.hasText(description.getGroupId()) + && StringUtils.hasText(description.getArtifactId())) { + return description.getGroupId() + "." + description.getArtifactId(); + } + return null; + } + + public Map getRequestedDependencies() { + return this.requestedDependencies; + } + + public Version getPlatformVersion() { + return this.platformVersion; + } + + public BuildSystem getBuildSystem() { + return this.buildSystem; + } + + public Packaging getPackaging() { + return this.packaging; + } + + public Language getLanguage() { + return this.language; + } + + public String getGroupId() { + return this.groupId; + } + + public String getArtifactId() { + return this.artifactId; + } + + public String getName() { + return this.name; + } + + public String getDescription() { + return this.description; + } + + public String getApplicationName() { + return this.applicationName; + } + + public String getPackageName() { + return this.packageName; + } + + public String getBaseDirectory() { + return this.baseDirectory; + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/MultipleResourcesProjectContributor.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/MultipleResourcesProjectContributor.java new file mode 100644 index 00000000..fda4619e --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/MultipleResourcesProjectContributor.java @@ -0,0 +1,72 @@ +/* + * 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 + * + * http://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.contributor; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.Predicate; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.util.FileCopyUtils; + +/** + * A {@link ProjectContributor} that contributes all of the resources found beneath a root + * location to a generated project. + * + * @author Andy Wilkinson + * @see PathMatchingResourcePatternResolver + */ +public class MultipleResourcesProjectContributor implements ProjectContributor { + + private final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + + private final String rootResource; + + private final Predicate executable; + + public MultipleResourcesProjectContributor(String rootResource) { + this(rootResource, (filename) -> false); + } + + public MultipleResourcesProjectContributor(String rootResource, + Predicate executable) { + this.rootResource = rootResource; + this.executable = executable; + } + + @Override + public void contribute(Path projectRoot) throws IOException { + Resource root = this.resolver.getResource(this.rootResource); + Resource[] resources = this.resolver.getResources(this.rootResource + "/**"); + for (Resource resource : resources) { + String filename = resource.getURI().toString() + .substring(root.getURI().toString().length() + 1); + if (resource.isReadable()) { + Path output = projectRoot.resolve(filename); + Files.createDirectories(output.getParent()); + Files.createFile(output); + FileCopyUtils.copy(resource.getInputStream(), + Files.newOutputStream(output)); + // TODO Set executable using NIO + output.toFile().setExecutable(this.executable.test(filename)); + } + } + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/ProjectContributor.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/ProjectContributor.java new file mode 100644 index 00000000..7c0a7e54 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/ProjectContributor.java @@ -0,0 +1,46 @@ +/* + * 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 + * + * http://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.contributor; + +import java.io.IOException; +import java.nio.file.Path; + +import org.springframework.core.Ordered; + +/** + * A callback for contributing on a generated project. Invoked with an {@link Ordered + * order} of {@code 0} by default, considering overriding {@link #getOrder()} to customize + * this behaviour. + * + * @author Andy Wilkinson + */ +@FunctionalInterface +public interface ProjectContributor extends Ordered { + + /** + * Contribute additional resources to the project in the specified root directory. + * @param projectRoot the root directory of the project + * @throws IOException if contributing a resource failed + */ + void contribute(Path projectRoot) throws IOException; + + @Override + default int getOrder() { + return 0; + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/SingleResourceProjectContributor.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/SingleResourceProjectContributor.java new file mode 100644 index 00000000..acae173c --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/SingleResourceProjectContributor.java @@ -0,0 +1,60 @@ +/* + * 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 + * + * http://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.contributor; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.util.FileCopyUtils; + +/** + * {@link ProjectContributor} that contributes a single file, identified by a resource + * pattern, to a generated project. + * + * @author Andy Wilkinson + * @see PathMatchingResourcePatternResolver + */ +public class SingleResourceProjectContributor implements ProjectContributor { + + private final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + + private final String filename; + + private final String resourcePattern; + + public SingleResourceProjectContributor(String filename, String resourcePattern) { + this.filename = filename; + this.resourcePattern = resourcePattern; + } + + @Override + public void contribute(Path projectRoot) throws IOException { + Path output = projectRoot.resolve(this.filename); + if (!Files.exists(output)) { + Files.createDirectories(output.getParent()); + Files.createFile(output); + } + Resource resource = this.resolver.getResource(this.resourcePattern); + FileCopyUtils.copy(resource.getInputStream(), + Files.newOutputStream(output, StandardOpenOption.APPEND)); + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/package-info.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/package-info.java new file mode 100644 index 00000000..3485e17a --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/contributor/package-info.java @@ -0,0 +1,20 @@ +/* + * 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 + * + * http://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. + */ + +/** + * Project contribution abstraction. + */ +package io.spring.initializr.generator.project.contributor; diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/package-info.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/package-info.java new file mode 100644 index 00000000..4626a66d --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/package-info.java @@ -0,0 +1,22 @@ +/* + * 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 + * + * http://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. + */ + +/** + * Project generation support classes. + * + * @see io.spring.initializr.generator.project.ProjectGenerator + */ +package io.spring.initializr.generator.project; diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnBuildSystemTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnBuildSystemTests.java new file mode 100644 index 00000000..73a33b3f --- /dev/null +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnBuildSystemTests.java @@ -0,0 +1,82 @@ +/* + * 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 + * + * http://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.condition; + +import io.spring.initializr.generator.buildsystem.gradle.GradleBuildSystem; +import io.spring.initializr.generator.buildsystem.maven.MavenBuildSystem; +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.test.project.ProjectAssetTester; +import org.junit.jupiter.api.Test; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConditionalOnBuildSystem}. + * + * @author Stephane Nicoll + */ +class ConditionalOnBuildSystemTests { + + private final ProjectAssetTester projectTester = new ProjectAssetTester() + .withConfiguration(BuildSystemTestConfiguration.class); + + @Test + void outcomeWithMavenBuildSystem() { + ProjectDescription projectDescription = new ProjectDescription(); + projectDescription.setBuildSystem(new MavenBuildSystem()); + String bean = outcomeFor(projectDescription); + assertThat(bean).isEqualTo("testMaven"); + } + + @Test + void outcomeWithGradleBuildSystem() { + ProjectDescription projectDescription = new ProjectDescription(); + projectDescription.setBuildSystem(new GradleBuildSystem()); + String bean = outcomeFor(projectDescription); + assertThat(bean).isEqualTo("testGradle"); + } + + private String outcomeFor(ProjectDescription projectDescription) { + return this.projectTester.generate(projectDescription, + (projectGenerationContext) -> { + assertThat(projectGenerationContext.getBeansOfType(String.class)) + .hasSize(1); + return projectGenerationContext.getBean(String.class); + }); + } + + @Configuration + static class BuildSystemTestConfiguration { + + @Bean + @ConditionalOnBuildSystem("gradle") + public String gradle() { + return "testGradle"; + } + + @Bean + @ConditionalOnBuildSystem("maven") + public String maven() { + return "testMaven"; + } + + } + +} diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnLanguageTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnLanguageTests.java new file mode 100644 index 00000000..9bfc848d --- /dev/null +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnLanguageTests.java @@ -0,0 +1,102 @@ +/* + * 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 + * + * http://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.condition; + +import io.spring.initializr.generator.language.groovy.GroovyLanguage; +import io.spring.initializr.generator.language.java.JavaLanguage; +import io.spring.initializr.generator.language.kotlin.KotlinLanguage; +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.test.project.ProjectAssetTester; +import org.junit.jupiter.api.Test; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConditionalOnLanguage}. + * + * @author Stephane Nicoll + */ +class ConditionalOnLanguageTests { + + private final ProjectAssetTester projectTester = new ProjectAssetTester() + .withConfiguration(LanguageTestConfiguration.class); + + @Test + void outcomeWithJavaLanguage() { + ProjectDescription projectDescription = new ProjectDescription(); + projectDescription.setLanguage(new JavaLanguage()); + String bean = outcomeFor(projectDescription); + assertThat(bean).isEqualTo("testJava"); + } + + @Test + void outcomeWithGroovyBuildSystem() { + ProjectDescription projectDescription = new ProjectDescription(); + projectDescription.setLanguage(new GroovyLanguage()); + String bean = outcomeFor(projectDescription); + assertThat(bean).isEqualTo("testGroovy"); + } + + @Test + void outcomeWithNoMatch() { + ProjectDescription projectDescription = new ProjectDescription(); + projectDescription.setLanguage(new KotlinLanguage()); + this.projectTester.generate(projectDescription, (projectGenerationContext) -> { + assertThat(projectGenerationContext.getBeansOfType(String.class)).isEmpty(); + return null; + }); + } + + @Test + void outcomeWithNoAvailableLanguage() { + ProjectDescription projectDescription = new ProjectDescription(); + this.projectTester.generate(projectDescription, (projectGenerationContext) -> { + assertThat(projectGenerationContext.getBeansOfType(String.class)).isEmpty(); + return null; + }); + } + + private String outcomeFor(ProjectDescription projectDescription) { + return this.projectTester.generate(projectDescription, + (projectGenerationContext) -> { + assertThat(projectGenerationContext.getBeansOfType(String.class)) + .hasSize(1); + return projectGenerationContext.getBean(String.class); + }); + } + + @Configuration + static class LanguageTestConfiguration { + + @Bean + @ConditionalOnLanguage("java") + public String java() { + return "testJava"; + } + + @Bean + @ConditionalOnLanguage("groovy") + public String groovy() { + return "testGroovy"; + } + + } + +} diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnPackagingTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnPackagingTests.java new file mode 100644 index 00000000..12142bcb --- /dev/null +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnPackagingTests.java @@ -0,0 +1,91 @@ +/* + * 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 + * + * http://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.condition; + +import io.spring.initializr.generator.packaging.jar.JarPackaging; +import io.spring.initializr.generator.packaging.war.WarPackaging; +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.test.project.ProjectAssetTester; +import org.junit.jupiter.api.Test; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConditionalOnPackaging}. + * + * @author Stephane Nicoll + */ +class ConditionalOnPackagingTests { + + private final ProjectAssetTester projectTester = new ProjectAssetTester() + .withConfiguration(PackagingTestConfiguration.class); + + @Test + void outcomeWithJarPackaging() { + ProjectDescription projectDescription = new ProjectDescription(); + projectDescription.setPackaging(new JarPackaging()); + String bean = outcomeFor(projectDescription); + assertThat(bean).isEqualTo("testJar"); + } + + @Test + void outcomeWithWarPackaging() { + ProjectDescription projectDescription = new ProjectDescription(); + projectDescription.setPackaging(new WarPackaging()); + String bean = outcomeFor(projectDescription); + assertThat(bean).isEqualTo("testWar"); + } + + @Test + void outcomeWithNoAvailablePackaging() { + ProjectDescription projectDescription = new ProjectDescription(); + this.projectTester.generate(projectDescription, (projectGenerationContext) -> { + assertThat(projectGenerationContext.getBeansOfType(String.class)).isEmpty(); + return null; + }); + } + + private String outcomeFor(ProjectDescription projectDescription) { + return this.projectTester.generate(projectDescription, + (projectGenerationContext) -> { + assertThat(projectGenerationContext.getBeansOfType(String.class)) + .hasSize(1); + return projectGenerationContext.getBean(String.class); + }); + } + + @Configuration + static class PackagingTestConfiguration { + + @Bean + @ConditionalOnPackaging("jar") + public String jar() { + return "testJar"; + } + + @Bean + @ConditionalOnPackaging("war") + public String war() { + return "testWar"; + } + + } + +} diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnPlatformVersionTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnPlatformVersionTests.java new file mode 100644 index 00000000..67727b13 --- /dev/null +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnPlatformVersionTests.java @@ -0,0 +1,108 @@ +/* + * 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 + * + * http://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.condition; + +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.test.project.ProjectAssetTester; +import io.spring.initializr.generator.version.Version; +import org.junit.jupiter.api.Test; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ConditionalOnPlatformVersion}. + * + * @author Stephane Nicoll + */ +class ConditionalOnPlatformVersionTests { + + private final ProjectAssetTester projectTester = new ProjectAssetTester() + .withConfiguration(PlatformVersionTestConfiguration.class); + + @Test + void outcomeWithMatchingRange() { + ProjectDescription projectDescription = new ProjectDescription(); + projectDescription.setPlatformVersion(Version.parse("1.2.0.RELEASE")); + String bean = outcomeFor(projectDescription); + assertThat(bean).isEqualTo("one"); + } + + @Test + void outcomeWithMatchingOpenRange() { + ProjectDescription projectDescription = new ProjectDescription(); + projectDescription.setPlatformVersion(Version.parse("2.0.1.RELEASE")); + String bean = outcomeFor(projectDescription); + assertThat(bean).isEqualTo("two"); + } + + @Test + void outcomeWithMatchingStartOfOpenRange() { + ProjectDescription projectDescription = new ProjectDescription(); + projectDescription.setPlatformVersion(Version.parse("2.0.0.M1")); + String bean = outcomeFor(projectDescription); + assertThat(bean).isEqualTo("two"); + } + + @Test + void outcomeWithNoMatch() { + ProjectDescription projectDescription = new ProjectDescription(); + projectDescription.setPlatformVersion(Version.parse("0.1.0")); + this.projectTester.generate(projectDescription, (projectGenerationContext) -> { + assertThat(projectGenerationContext.getBeansOfType(String.class)).isEmpty(); + return null; + }); + } + + @Test + void outcomeWithNoAvailablePlatformVersion() { + ProjectDescription projectDescription = new ProjectDescription(); + this.projectTester.generate(projectDescription, (projectGenerationContext) -> { + assertThat(projectGenerationContext.getBeansOfType(String.class)).isEmpty(); + return null; + }); + } + + private String outcomeFor(ProjectDescription projectDescription) { + return this.projectTester.generate(projectDescription, + (projectGenerationContext) -> { + assertThat(projectGenerationContext.getBeansOfType(String.class)) + .hasSize(1); + return projectGenerationContext.getBean(String.class); + }); + } + + @Configuration + static class PlatformVersionTestConfiguration { + + @Bean + @ConditionalOnPlatformVersion("[1.0.0.RELEASE, 2.0.0.M1)") + public String first() { + return "one"; + } + + @Bean + @ConditionalOnPlatformVersion("2.0.0.M1") + public String second() { + return "two"; + } + + } + +} diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnRequestedDependencyTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnRequestedDependencyTests.java new file mode 100644 index 00000000..36329fb7 --- /dev/null +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/condition/ConditionalOnRequestedDependencyTests.java @@ -0,0 +1,74 @@ +/* + * 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 + * + * http://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.condition; + +import io.spring.initializr.generator.buildsystem.Dependency; +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.test.project.ProjectAssetTester; +import org.junit.jupiter.api.Test; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link ConditionalOnRequestedDependency}. + * + * @author Stephane Nicoll + */ +class ConditionalOnRequestedDependencyTests { + + private final ProjectAssetTester projectTester = new ProjectAssetTester() + .withConfiguration(RequestedDependencyTestConfiguration.class); + + @Test + void outcomeWithMatchingDependency() { + ProjectDescription projectDescription = new ProjectDescription(); + projectDescription.addDependency("web", mock(Dependency.class)); + String bean = this.projectTester.generate(projectDescription, + (projectGenerationContext) -> { + assertThat(projectGenerationContext.getBeansOfType(String.class)) + .hasSize(1); + return projectGenerationContext.getBean(String.class); + }); + assertThat(bean).isEqualTo("webDependency"); + } + + @Test + void outcomeWithNoMatch() { + ProjectDescription projectDescription = new ProjectDescription(); + projectDescription.addDependency("another", mock(Dependency.class)); + this.projectTester.generate(projectDescription, (projectGenerationContext) -> { + assertThat(projectGenerationContext.getBeansOfType(String.class)).isEmpty(); + return null; + }); + } + + @Configuration + static class RequestedDependencyTestConfiguration { + + @Bean + @ConditionalOnRequestedDependency("web") + public String webActive() { + return "webDependency"; + } + + } + +} 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 new file mode 100644 index 00000000..8b28150c --- /dev/null +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/project/ProjectGeneratorTests.java @@ -0,0 +1,134 @@ +/* + * 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 + * + * http://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; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.function.Consumer; + +import io.spring.initializr.generator.buildsystem.maven.MavenBuildSystem; +import io.spring.initializr.generator.project.contributor.ProjectContributor; +import io.spring.initializr.generator.test.project.ProjectGeneratorTester; +import io.spring.initializr.generator.version.Version; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ProjectGenerator}. + * + * @author Andy Wilkinson + * @author Stephane Nicoll + */ +class ProjectGeneratorTests { + + private final ProjectGeneratorTester projectTester = new ProjectGeneratorTester() + .withDescriptionCustomizer((description) -> { + description.setBuildSystem(new MavenBuildSystem()); + description.setPlatformVersion(Version.parse("2.1.0.RELEASE")); + }); + + @Test + void generateInvokedProcessor() { + ProjectDescription description = new ProjectDescription(); + description.setBuildSystem(new MavenBuildSystem()); + Version platformVersion = Version.parse("2.1.0.RELEASE"); + description.setPackageName("com.example.test"); + ResolvedProjectDescription resolvedProjectDescription = this.projectTester + .generate(description, + (projectGenerationContext) -> projectGenerationContext + .getBean(ResolvedProjectDescription.class)); + assertThat(resolvedProjectDescription.getPlatformVersion()) + .isEqualTo(platformVersion); + assertThat(resolvedProjectDescription.getPackageName()) + .isEqualTo("com.example.test"); + } + + @Test + void generateInvokesCustomizers() { + ProjectGeneratorTester tester = this.projectTester + .withContextInitializer((context) -> { + context.registerBean("customizer1", + TestProjectDescriptionCustomizer.class, + () -> new TestProjectDescriptionCustomizer(5, + (description) -> description.setName("Test"))); + context.registerBean("customizer2", + TestProjectDescriptionCustomizer.class, + () -> new TestProjectDescriptionCustomizer(3, + (description) -> { + description.setName("First"); + description.setGroupId("com.acme"); + })); + }); + ProjectDescription description = new ProjectDescription(); + description.setGroupId("com.example.demo"); + description.setName("Original"); + + ResolvedProjectDescription resolvedProjectDescription = tester.generate( + description, (projectGenerationContext) -> projectGenerationContext + .getBean(ResolvedProjectDescription.class)); + assertThat(resolvedProjectDescription.getGroupId()).isEqualTo("com.acme"); + assertThat(resolvedProjectDescription.getName()).isEqualTo("Test"); + } + + @Test + void generateInvokeProjectContributors(@TempDir Path directory) { + ProjectGeneratorTester tester = this.projectTester.withDirectory(directory) + .withContextInitializer((context) -> { + context.registerBean("contributor1", ProjectContributor.class, + () -> (projectDirectory) -> Files + .createFile(projectDirectory.resolve("test.text"))); + context.registerBean("contributor2", ProjectContributor.class, + () -> (projectDirectory) -> { + Path subDir = projectDirectory.resolve("src/main/test"); + Files.createDirectories(subDir); + Files.createFile(subDir.resolve("Test.src")); + }); + }); + List relativePaths = tester.generate(new ProjectDescription()) + .getRelativePathsOfProjectFiles(); + assertThat(relativePaths).containsOnly("test.text", "src/main/test/Test.src"); + } + + private static class TestProjectDescriptionCustomizer + implements ProjectDescriptionCustomizer { + + private final Integer order; + + private final Consumer projectDescription; + + TestProjectDescriptionCustomizer(Integer order, + Consumer projectDescription) { + this.order = order; + this.projectDescription = projectDescription; + } + + @Override + public void customize(ProjectDescription description) { + this.projectDescription.accept(description); + } + + @Override + public int getOrder() { + return this.order; + } + + } + +} diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/test/project/AbstractProjectGenerationTester.java b/initializr-generator/src/test/java/io/spring/initializr/generator/test/project/AbstractProjectGenerationTester.java new file mode 100644 index 00000000..a64fecd4 --- /dev/null +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/test/project/AbstractProjectGenerationTester.java @@ -0,0 +1,143 @@ +/* + * 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 + * + * http://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.test.project; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import io.spring.initializr.generator.io.IndentingWriterFactory; +import io.spring.initializr.generator.io.SimpleIndentStrategy; +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.project.ProjectDirectoryFactory; +import io.spring.initializr.generator.project.ProjectGenerationContext; +import io.spring.initializr.generator.project.ProjectGenerationException; + +/** + * Base tester for project generation. + * + * @author Stephane Nicoll + */ +public abstract class AbstractProjectGenerationTester> { + + private final Map, Supplier> beanDefinitions; + + private final Consumer contextInitializer; + + private final Consumer descriptionCustomizer; + + protected AbstractProjectGenerationTester( + Map, Supplier> beanDefinitions) { + this(beanDefinitions, defaultContextInitializer(), + defaultDescriptionCustomizer()); + } + + protected AbstractProjectGenerationTester(Map, Supplier> beanDefinitions, + Consumer contextInitializer, + Consumer descriptionCustomizer) { + this.beanDefinitions = new LinkedHashMap<>(beanDefinitions); + this.descriptionCustomizer = descriptionCustomizer; + this.contextInitializer = contextInitializer; + } + + protected abstract SELF newInstance(Map, Supplier> beanDefinitions, + Consumer contextInitializer, + Consumer descriptionCustomizer); + + public SELF withBean(Class beanType, Supplier beanDefinition) { + LinkedHashMap, Supplier> beans = new LinkedHashMap<>( + this.beanDefinitions); + beans.put(beanType, beanDefinition); + return newInstance(beans, this.contextInitializer, this.descriptionCustomizer); + } + + public SELF withDirectory(Path directory) { + return withBean(ProjectDirectoryFactory.class, + () -> (description) -> Files.createTempDirectory(directory, "project-")); + } + + public SELF withIndentingWriterFactory() { + return withBean(IndentingWriterFactory.class, + () -> IndentingWriterFactory.create(new SimpleIndentStrategy(" "))); + } + + public SELF withContextInitializer(Consumer context) { + return newInstance(this.beanDefinitions, this.contextInitializer.andThen(context), + this.descriptionCustomizer); + } + + public SELF withDescriptionCustomizer(Consumer description) { + return newInstance(this.beanDefinitions, this.contextInitializer, + this.descriptionCustomizer.andThen(description)); + } + + protected static Consumer defaultContextInitializer() { + return (context) -> { + }; + } + + protected static Consumer defaultDescriptionCustomizer() { + return (projectDescription) -> { + if (projectDescription.getGroupId() == null) { + projectDescription.setGroupId("com.example"); + } + if (projectDescription.getArtifactId() == null) { + projectDescription.setArtifactId("demo"); + } + if (projectDescription.getApplicationName() == null) { + projectDescription.setApplicationName("DemoApplication"); + } + }; + } + + protected T invokeProjectGeneration(ProjectDescription description, + ProjectGenerationInvoker invoker) { + this.descriptionCustomizer.accept(description); + try { + return invoker.generate(beansConfigurer().andThen(this.contextInitializer)); + } + catch (IOException ex) { + throw new ProjectGenerationException("Failed to generated project", ex); + } + } + + private Consumer beansConfigurer() { + return (context) -> { + this.beanDefinitions.forEach( + (type, definition) -> register(context, type, definition.get())); + }; + } + + // Restore proper generic signature to make sure the context resolve the bean properly + private void register(ProjectGenerationContext context, Class type, + Object instance) { + T bean = type.cast(instance); + context.registerBean(type, () -> bean); + } + + protected interface ProjectGenerationInvoker { + + T generate(Consumer contextInitializer) + throws IOException; + + } + +} diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/test/project/ProjectAssetTester.java b/initializr-generator/src/test/java/io/spring/initializr/generator/test/project/ProjectAssetTester.java new file mode 100644 index 00000000..9709237e --- /dev/null +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/test/project/ProjectAssetTester.java @@ -0,0 +1,118 @@ +/* + * 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 + * + * http://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.test.project; + +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import io.spring.initializr.generator.project.ProjectAssetGenerator; +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.project.ProjectDirectoryFactory; +import io.spring.initializr.generator.project.ProjectGenerationContext; +import io.spring.initializr.generator.project.ResolvedProjectDescription; +import io.spring.initializr.generator.project.contributor.ProjectContributor; + +/** + * A tester for project asset that does not detect available {@link ProjectContributor + * contributors}. By default, no contributor is available and can be added using a + * {@link #withConfiguration(Class[]) configuration class} or a + * {@link #withContextInitializer(Consumer) customization of the project generation + * context}. + * + * @author Stephane Nicoll + */ +public class ProjectAssetTester + extends AbstractProjectGenerationTester { + + public ProjectAssetTester() { + super(Collections.emptyMap()); + } + + private ProjectAssetTester(Map, Supplier> beanDefinitions, + Consumer contextInitializer, + Consumer descriptionCustomizer) { + super(beanDefinitions, contextInitializer, descriptionCustomizer); + } + + @Override + protected ProjectAssetTester newInstance(Map, Supplier> beanDefinitions, + Consumer contextInitializer, + Consumer descriptionCustomizer) { + return new ProjectAssetTester(beanDefinitions, contextInitializer, + descriptionCustomizer); + } + + public ProjectAssetTester withConfiguration(Class... configurationClasses) { + return withContextInitializer( + (context) -> context.register(configurationClasses)); + } + + /** + * Generate a project asset using the specified {@link ProjectAssetGenerator}. + * @param description the description of the project to generate + * @param projectAssetGenerator the {@link ProjectAssetGenerator} to invoke + * @param the project asset type + * @return the project asset + * @see #withConfiguration(Class[]) + */ + public T generate(ProjectDescription description, + ProjectAssetGenerator projectAssetGenerator) { + return invokeProjectGeneration(description, (contextInitializer) -> { + try (ProjectGenerationContext context = new ProjectGenerationContext()) { + ResolvedProjectDescription resolvedProjectDescription = new ResolvedProjectDescription( + description); + context.registerBean(ResolvedProjectDescription.class, + () -> resolvedProjectDescription); + contextInitializer.accept(context); + context.refresh(); + return projectAssetGenerator.generate(context); + } + }); + } + + /** + * Generate a project structure using only explicitly configured + * {@link ProjectContributor contributors}. + * @param description the description of the project to generateProject + * @return the {@link ProjectStructure} of the generated project + * @see #withConfiguration(Class[]) + */ + public ProjectStructure generate(ProjectDescription description) { + return generate(description, runAllAvailableContributors()); + } + + private ProjectAssetGenerator runAllAvailableContributors() { + return (context) -> { + Path projectDirectory = context.getBean(ProjectDirectoryFactory.class) + .createProjectDirectory( + context.getBean(ResolvedProjectDescription.class)); + List projectContributors = context + .getBeanProvider(ProjectContributor.class).orderedStream() + .collect(Collectors.toList()); + for (ProjectContributor projectContributor : projectContributors) { + projectContributor.contribute(projectDirectory); + } + return new ProjectStructure(projectDirectory); + }; + } + +} diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/test/project/ProjectGeneratorTester.java b/initializr-generator/src/test/java/io/spring/initializr/generator/test/project/ProjectGeneratorTester.java new file mode 100644 index 00000000..a5a2ef4b --- /dev/null +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/test/project/ProjectGeneratorTester.java @@ -0,0 +1,80 @@ +/* + * 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 + * + * http://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.test.project; + +import java.nio.file.Path; +import java.util.Collections; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import io.spring.initializr.generator.io.IndentingWriterFactory; +import io.spring.initializr.generator.io.SimpleIndentStrategy; +import io.spring.initializr.generator.project.DefaultProjectAssetGenerator; +import io.spring.initializr.generator.project.ProjectAssetGenerator; +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.project.ProjectGenerationContext; +import io.spring.initializr.generator.project.ProjectGenerator; + +/** + * A tester class for {@link ProjectGenerator}. + * + * @author Stephane Nicoll + */ +public class ProjectGeneratorTester + extends AbstractProjectGenerationTester { + + private ProjectGeneratorTester(Map, Supplier> beanDefinitions, + Consumer contextInitializer, + Consumer descriptionCustomizer) { + super(beanDefinitions, contextInitializer, descriptionCustomizer); + } + + public ProjectGeneratorTester() { + super(defaultBeans()); + } + + private static Map, Supplier> defaultBeans() { + return Collections.singletonMap(IndentingWriterFactory.class, + () -> IndentingWriterFactory.create(new SimpleIndentStrategy(" "))); + } + + @Override + protected ProjectGeneratorTester newInstance( + Map, Supplier> beanDefinitions, + Consumer contextInitializer, + Consumer descriptionCustomizer) { + return new ProjectGeneratorTester(beanDefinitions, contextInitializer, + descriptionCustomizer); + } + + public ProjectStructure generate(ProjectDescription description) { + return invokeProjectGeneration(description, (contextInitializer) -> { + Path directory = new ProjectGenerator(contextInitializer) + .generate(description, new DefaultProjectAssetGenerator()); + return new ProjectStructure(directory); + }); + } + + public T generate(ProjectDescription description, + ProjectAssetGenerator projectAssetGenerator) { + return invokeProjectGeneration(description, + (contextInitializer) -> new ProjectGenerator(contextInitializer) + .generate(description, projectAssetGenerator)); + } + +} diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/test/project/ProjectStructure.java b/initializr-generator/src/test/java/io/spring/initializr/generator/test/project/ProjectStructure.java new file mode 100644 index 00000000..ee939a5b --- /dev/null +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/test/project/ProjectStructure.java @@ -0,0 +1,85 @@ +/* + * 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 + * + * http://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.test.project; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; + +/** + * Test helper to assert content of a generated project structure. + * + * @author Stephane Nicoll + */ +public class ProjectStructure { + + private final Path projectDirectory; + + /** + * Create an instance based on the specified project {@link Path directory}. + * @param projectDirectory the project's root directory + */ + public ProjectStructure(Path projectDirectory) { + this.projectDirectory = projectDirectory; + } + + /** + * Return the project directory. + * @return the project directory + */ + public Path getProjectDirectory() { + return this.projectDirectory; + } + + /** + * Resolve a {@link Path} relative to the project directory + * @param other the path string to resolve against the root of the project structure + * @return the resulting path + * @see Path#resolve(String) + */ + public Path resolve(String other) { + return this.projectDirectory.resolve(other); + } + + /** + * Return the relative paths of all files. + * @return the relative path of all files + */ + public List getRelativePathsOfProjectFiles() { + List relativePaths = new ArrayList<>(); + try { + Files.walkFileTree(this.projectDirectory, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + relativePaths.add(ProjectStructure.this.projectDirectory + .relativize(file).toString()); + return FileVisitResult.CONTINUE; + } + }); + } + catch (IOException ex) { + throw new IllegalStateException(ex); + } + return relativePaths; + } + +}