diff --git a/initializr/src/main/groovy/io/spring/initializr/ProjectGenerator.groovy b/initializr/src/main/groovy/io/spring/initializr/ProjectGenerator.groovy index f42865c5..ee6d3771 100644 --- a/initializr/src/main/groovy/io/spring/initializr/ProjectGenerator.groovy +++ b/initializr/src/main/groovy/io/spring/initializr/ProjectGenerator.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2015 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. @@ -17,6 +17,7 @@ package io.spring.initializr import groovy.util.logging.Slf4j +import io.spring.initializr.support.Version import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value @@ -34,6 +35,8 @@ import static io.spring.initializr.support.GroovyTemplate.template @Slf4j class ProjectGenerator { + private static final VERSION_1_2_0_RC1 = Version.parse('1.2.0.RC1') + @Autowired InitializrMetadata metadata @@ -164,6 +167,10 @@ class ProjectGenerator { log.info("Processing request{type=$request.type, dependencies=$dependencies}") request.properties.each { model[it.key] = it.value } + + // @SpringBootApplication available as from 1.2.0.RC1 + model['useSpringBootApplication'] = VERSION_1_2_0_RC1 + .compareTo(Version.safeParse(request.bootVersion)) <= 0 model } diff --git a/initializr/src/main/groovy/io/spring/initializr/support/Version.groovy b/initializr/src/main/groovy/io/spring/initializr/support/Version.groovy new file mode 100644 index 00000000..082e6684 --- /dev/null +++ b/initializr/src/main/groovy/io/spring/initializr/support/Version.groovy @@ -0,0 +1,169 @@ +/* + * Copyright 2012-2015 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.support + +import groovy.transform.EqualsAndHashCode +import groovy.transform.ToString + +/** + * Define the version number of a module. A typical version is represented + * as {@code MAJOR.MINOR.PATCH.QUALIFER} where the qualifier can have an + * extra version. + *

+ * For example: {@code 1.2.0.RC1} is the first release candidate of 1.2.0 + * and {@code 1.5.0.M4} is the fourth milestone of 1.5.0. The special + * {@code RELEASE} qualifier indicates a final release (a.k.a. GA) + *

+ * The main purpose of parsing a version is to compare it with another + * version, see {@link Comparable}. + * + * @author Stephane Nicoll + * @since 1.0 + */ +@ToString +@EqualsAndHashCode +class Version implements Comparable { + + private static final String VERSION_REGEX = '^(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.([^0-9]+)(\\d+)?)?$' + + private static final VersionQualifierComparator qualifierComparator = new VersionQualifierComparator() + + Integer major + Integer minor + Integer patch + Qualifier qualifier + + /** + * Parse the string representation of a {@link Version}. Throws an + * {@link IllegalArgumentException} if the version could not be parsed. + * @param text the version text + * @return a Version instance for the specified version text + * @throws IllegalArgumentException if the version text could not be parsed + * @see #safeParse(java.lang.String) + */ + static Version parse(String text) { + def matcher = (text =~ VERSION_REGEX) + if (!matcher.matches()) { + throw new IllegalArgumentException("Could not determine version based on $text") + } + Version version = new Version() + version.major = Integer.valueOf(matcher[0][1]) + version.minor = Integer.valueOf(matcher[0][2]) + version.patch = Integer.valueOf(matcher[0][3]) + String qualifierId = matcher[0][4] + if (qualifierId) { + Qualifier qualifier = new Qualifier(qualifier: qualifierId) + String o = matcher[0][5] + if (o != null) { + qualifier.version = Integer.valueOf(o) + } + version.qualifier = qualifier + } + version + } + + /** + * Parse safely the specified string representation of a {@link Version}. + *

+ * Return {@code null} if the text represents an invalid version. + * @param text the version text + * @return a Version instance for the specified version text + * @see #parse(java.lang.String) + */ + static safeParse(String text) { + try { + return parse(text) + } catch (IllegalArgumentException e) { + return null + } + } + + @Override + int compareTo(Version other) { + if (other == null) { + return 1 + } + int majorDiff = safeCompare(this.major, other.major) + if (majorDiff != 0) { + return majorDiff + } + int minorDiff = safeCompare(this.minor, other.minor) + if (minorDiff != 0) { + return minorDiff + } + int patch = safeCompare(this.patch, other.patch) + if (patch != 0) { + return patch + } + qualifierComparator.compare(this.qualifier, other.qualifier) + } + + private static int safeCompare(Integer first, Integer second) { + int firstIndex = first ?: 0 + int secondIndex = second ?: 0 + return firstIndex.compareTo(secondIndex) + } + + @ToString + @EqualsAndHashCode + public static class Qualifier { + String qualifier + Integer version + } + + + private static class VersionQualifierComparator implements Comparator { + + static final String RELEASE = 'RELEASE' + static final String SNAPSHOT = 'BUILD-SNAPSHOT' + static final String MILESTONE = 'M' + static final String RC = 'RC' + + static final List KNOWN_QUALIFIERS = Arrays.asList(MILESTONE, RC, SNAPSHOT, RELEASE) + + @Override + int compare(Qualifier o1, Qualifier o2) { + Qualifier first = o1 ?: new Qualifier(qualifier: RELEASE) + Qualifier second = o2 ?: new Qualifier(qualifier: RELEASE) + + int qualifier = compareQualifier(first, second) + qualifier ? qualifier : compareQualifierVersion(first, second) + } + + private static int compareQualifierVersion(Qualifier first, Qualifier second) { + int firstVersion = first.getVersion() ?: 0 + int secondVersion = second.getVersion() ?: 0 + firstVersion.compareTo(secondVersion) + } + + private static int compareQualifier(Qualifier first, Qualifier second) { + int firstIndex = getQualifierIndex(first.qualifier) + int secondIndex = getQualifierIndex(second.qualifier) + + if (firstIndex == -1 && secondIndex == -1) { // Unknown qualifier, alphabetic ordering + return first.qualifier.compareTo(second.qualifier) + } else { + return firstIndex.compareTo(secondIndex) + } + } + + private static int getQualifierIndex(String qualifier) { + qualifier ? KNOWN_QUALIFIERS.indexOf(qualifier) : RELEASE + } + } + +} diff --git a/initializr/src/main/resources/templates/Application.groovy.tmpl b/initializr/src/main/resources/templates/Application.groovy.tmpl index ac2f49a8..f152ed56 100644 --- a/initializr/src/main/resources/templates/Application.groovy.tmpl +++ b/initializr/src/main/resources/templates/Application.groovy.tmpl @@ -1,13 +1,15 @@ package ${packageName} -import org.springframework.boot.SpringApplication +import org.springframework.boot.SpringApplication<% if (useSpringBootApplication) { %> +import org.springframework.boot.autoconfigure.SpringBootApplication<% } else { %> import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.context.annotation.ComponentScan -import org.springframework.context.annotation.Configuration - +import org.springframework.context.annotation.Configuration<% } %> +<% if (useSpringBootApplication) { %> +@SpringBootApplication<% } else { %> @Configuration @ComponentScan -@EnableAutoConfiguration +@EnableAutoConfiguration <% } %> class ${applicationName} { static void main(String[] args) { diff --git a/initializr/src/main/resources/templates/Application.java b/initializr/src/main/resources/templates/Application.java index 20029b2d..0b88899d 100644 --- a/initializr/src/main/resources/templates/Application.java +++ b/initializr/src/main/resources/templates/Application.java @@ -1,13 +1,15 @@ package ${packageName}; -import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringApplication;<% if (useSpringBootApplication) { %> +import org.springframework.boot.autoconfigure.SpringBootApplication;<% } else { %> import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; - +import org.springframework.context.annotation.Configuration;<% } %> +<% if (useSpringBootApplication) { %> +@SpringBootApplication<% } else { %> @Configuration @ComponentScan -@EnableAutoConfiguration +@EnableAutoConfiguration <% } %> public class ${applicationName} { public static void main(String[] args) { diff --git a/initializr/src/test/groovy/io/spring/initializr/ProjectGeneratorTests.groovy b/initializr/src/test/groovy/io/spring/initializr/ProjectGeneratorTests.groovy index 42bbc067..d141c406 100644 --- a/initializr/src/test/groovy/io/spring/initializr/ProjectGeneratorTests.groovy +++ b/initializr/src/test/groovy/io/spring/initializr/ProjectGeneratorTests.groovy @@ -25,6 +25,11 @@ import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration + import static org.mockito.Mockito.* /** @@ -146,6 +151,60 @@ class ProjectGeneratorTests { } + @Test + void springBoot11UseEnableAutoConfigurationJava() { + def request = createProjectRequest('web') + request.bootVersion = '1.1.9.RELEASE' + request.name = 'MyDemo' + request.packageName = 'foo' + generateProject(request).sourceCodeAssert('src/main/java/foo/MyDemoApplication.java') + .hasImports(EnableAutoConfiguration.class.name, ComponentScan.class.name, Configuration.class.name) + .doesNotHaveImports(SpringBootApplication.class.name) + .contains('@EnableAutoConfiguration', '@Configuration', '@ComponentScan') + .doesNotContain('@SpringBootApplication') + } + + @Test + void springBootUseSpringBootApplicationJava() { + def request = createProjectRequest('web') + request.bootVersion = '1.2.0.RC1' + request.name = 'MyDemo' + request.packageName = 'foo' + generateProject(request).sourceCodeAssert('src/main/java/foo/MyDemoApplication.java') + .hasImports(SpringBootApplication.class.name) + .doesNotHaveImports(EnableAutoConfiguration.class.name, ComponentScan.class.name, Configuration.class.name) + .contains('@SpringBootApplication') + .doesNotContain('@EnableAutoConfiguration', '@Configuration', '@ComponentScan') + } + + @Test + void springBoot11UseEnableAutoConfigurationGroovy() { + def request = createProjectRequest('web') + request.language = 'groovy' + request.bootVersion = '1.1.9.RELEASE' + request.name = 'MyDemo' + request.packageName = 'foo' + generateProject(request).sourceCodeAssert('src/main/groovy/foo/MyDemoApplication.groovy') + .hasImports(EnableAutoConfiguration.class.name, ComponentScan.class.name, Configuration.class.name) + .doesNotHaveImports(SpringBootApplication.class.name) + .contains('@EnableAutoConfiguration', '@Configuration', '@ComponentScan') + .doesNotContain('@SpringBootApplication') + } + + @Test + void springBootUseSpringBootApplicationGroovy() { + def request = createProjectRequest('web') + request.language = 'groovy' + request.bootVersion = '1.2.0.RC1' + request.name = 'MyDemo' + request.packageName = 'foo' + generateProject(request).sourceCodeAssert('src/main/groovy/foo/MyDemoApplication.groovy') + .hasImports(SpringBootApplication.class.name) + .doesNotHaveImports(EnableAutoConfiguration.class.name, ComponentScan.class.name, Configuration.class.name) + .contains('@SpringBootApplication') + .doesNotContain('@EnableAutoConfiguration', '@Configuration', '@ComponentScan') + } + PomAssert generateMavenPom(ProjectRequest request) { def content = new String(projectGenerator.generateMavenPom(request)) new PomAssert(content).validateProjectRequest(request) diff --git a/initializr/src/test/groovy/io/spring/initializr/support/VersionTests.groovy b/initializr/src/test/groovy/io/spring/initializr/support/VersionTests.groovy new file mode 100644 index 00000000..49243222 --- /dev/null +++ b/initializr/src/test/groovy/io/spring/initializr/support/VersionTests.groovy @@ -0,0 +1,142 @@ +/* + * Copyright 2012-2015 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.support + +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException + +import static io.spring.initializr.support.Version.parse +import static io.spring.initializr.support.Version.safeParse +import static org.hamcrest.MatcherAssert.assertThat +import static org.hamcrest.Matchers.* +import static org.junit.Assert.assertNull + +/** + * @author Stephane Nicoll + */ +class VersionTests { + + @Rule + public final ExpectedException thrown = ExpectedException.none() + + @Test + void equalNoQualifier() { + def first = parse('1.2.0') + def second = parse('1.2.0') + assertThat(first, comparesEqualTo(second)) + assertThat(first, equalTo(second)) + } + + @Test + void equalQualifierNoVersion() { + def first = parse('1.2.0.RELEASE') + def second = parse('1.2.0.RELEASE') + assertThat(first, comparesEqualTo(second)) + assertThat(first, equalTo(second)) + } + + @Test + void equalQualifierVersion() { + def first = parse('1.2.0.RC1') + def second = parse('1.2.0.RC1') + assertThat(first, comparesEqualTo(second)) + assertThat(first, equalTo(second)) + } + + @Test + void compareMajorOnly() { + assertThat(parse('2.2.0'), greaterThan(parse('1.8.0'))) + } + + @Test + void compareMinorOnly() { + assertThat(parse('2.2.0'), greaterThan(parse('2.1.9'))) + } + + @Test + void comparePatchOnly() { + assertThat(parse('2.2.4'), greaterThan(parse('2.2.3'))) + } + + @Test + void compareHigherVersion() { + assertThat(parse('1.2.0.RELEASE'), greaterThan(parse('1.1.9.RELEASE'))) + } + + @Test + void compareHigherQualifier() { + assertThat(parse('1.2.0.RC1'), greaterThan(parse('1.2.0.M1'))) + } + + @Test + void compareHigherQualifierVersion() { + assertThat(parse('1.2.0.RC2'), greaterThan(parse('1.2.0.RC1'))) + } + + @Test + void compareLowerVersion() { + assertThat(parse('1.0.5.RELEASE'), lessThan(parse('1.1.9.RELEASE'))) + } + + @Test + void compareLowerQualifier() { + assertThat(parse('1.2.0.RC1'), lessThan(parse('1.2.0.RELEASE'))) + } + + @Test + void compareLessQualifierVersion() { + assertThat(parse('1.2.0.RC2'), lessThan(parse('1.2.0.RC3'))) + } + + @Test + void compareWithNull() { + assertThat(parse('1.2.0.RC2'), greaterThan(null)) + } + + @Test + void compareUnknownQualifier() { + assertThat(parse('1.2.0.Beta'), lessThan(parse('1.2.0.CR'))) + } + + @Test + void compareUnknownQualifierVersion() { + assertThat(parse('1.2.0.Beta1'), lessThan(parse('1.2.0.Beta2'))) + } + + @Test + void snapshotGreaterThanRC() { + assertThat(parse('1.2.0.BUILD-SNAPSHOT'), greaterThan(parse('1.2.0.RC1'))) + } + + @Test + void snapshotLowerThanRelease() { + assertThat(parse('1.2.0.BUILD-SNAPSHOT'), lessThan(parse('1.2.0.RELEASE'))) + } + + @Test + void parseInvalidVersion() { + thrown.expect(IllegalArgumentException) + parse('foo') + } + + @Test + void safeParseInvalidVersion() { + assertNull safeParse('foo') + } + +} diff --git a/initializr/src/test/groovy/io/spring/initializr/test/ProjectAssert.groovy b/initializr/src/test/groovy/io/spring/initializr/test/ProjectAssert.groovy index b0de0d5b..47ad9de1 100644 --- a/initializr/src/test/groovy/io/spring/initializr/test/ProjectAssert.groovy +++ b/initializr/src/test/groovy/io/spring/initializr/test/ProjectAssert.groovy @@ -47,6 +47,14 @@ class ProjectAssert { new PomAssert(file('pom.xml').text) } + /** + * Return a {@link SourceCodeAssert} for the specified source code. + */ + SourceCodeAssert sourceCodeAssert(String sourceCodePath) { + hasFile(sourceCodePath) + new SourceCodeAssert(sourceCodePath, file(sourceCodePath).text) + } + ProjectAssert isMavenProject() { hasFile('pom.xml').hasNoFile('build.gradle') } diff --git a/initializr/src/test/groovy/io/spring/initializr/test/SourceCodeAssert.groovy b/initializr/src/test/groovy/io/spring/initializr/test/SourceCodeAssert.groovy new file mode 100644 index 00000000..74eb990e --- /dev/null +++ b/initializr/src/test/groovy/io/spring/initializr/test/SourceCodeAssert.groovy @@ -0,0 +1,66 @@ +/* + * Copyright 2012-2015 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.test + +import static org.junit.Assert.assertFalse +import static org.junit.Assert.assertTrue + +/** + * Source code assertions. + * + * @author Stephane Nicoll + * @since 1.0 + */ +class SourceCodeAssert { + + private final String name + private final String content + + SourceCodeAssert(String name, String content) { + this.name = name + this.content = content + } + + SourceCodeAssert hasImports(String... classNames) { + for (String className : classNames) { + contains("import $className") + } + this + } + + SourceCodeAssert doesNotHaveImports(String... classNames) { + for (String className : classNames) { + doesNotContain("import $className") + } + this + } + + SourceCodeAssert contains(String... expressions) { + for (String expression : expressions) { + assertTrue "$expression has not been found in source code '$name'", content.contains(expression) + } + this + } + + SourceCodeAssert doesNotContain(String... expressions) { + for (String expression : expressions) { + assertFalse "$expression should not have been found in source code '$name'", content.contains(expression) + } + this + } + +}