Use project name to infer the name of the app

The 'name' field can now be used to customize the name of the generated
application (and its test counterpart). A default application name is
used If no suitable identifier can be inferred from the specified name.

Concretely, this means that the default application name is no longer
`Application` but `DemoApplication` since the default name is `demo`.

Closes gh-60
This commit is contained in:
Stephane Nicoll 2014-12-19 12:19:01 +01:00
parent 4ab189539e
commit fd85022ef6
15 changed files with 213 additions and 33 deletions

View File

@ -297,10 +297,13 @@ class InitializrMetadata {
}
static class Defaults {
static final String DEFAULT_NAME = 'demo'
String groupId = 'org.test'
String artifactId
String version = '0.0.1-SNAPSHOT'
String name = 'demo'
String name = DEFAULT_NAME
String description = 'Demo project for Spring Boot'
String packageName
String type

View File

@ -84,14 +84,16 @@ class ProjectGenerator {
new File(dir, 'pom.xml').write(pom)
}
def applicationName = request.applicationName
def language = request.language
def src = new File(new File(dir, "src/main/$language"), request.packageName.replace('.', '/'))
src.mkdirs()
write(src, "Application.$language", model)
write(new File(src, "${applicationName}.${language}"), "Application.$language", model)
if (request.packaging == 'war') {
write(src, "ServletInitializer.$language", model)
def fileName = "ServletInitializer.$language"
write(new File(src, fileName), fileName, model)
}
def test = new File(new File(dir, "src/test/$language"), request.packageName.replace('.', '/'))
@ -103,7 +105,7 @@ class ProjectGenerator {
model.testAnnotations = ''
model.testImports = ''
}
write(test, "ApplicationTests.$language", model)
write(new File(test, "${applicationName}Tests.${language}"), "ApplicationTests.$language", model)
def resources = new File(dir, 'src/main/resources')
resources.mkdirs()
@ -174,10 +176,10 @@ class ProjectGenerator {
template 'starter-build.gradle', model
}
def write(File src, String name, def model) {
def tmpl = name.endsWith('.groovy') ? name + '.tmpl' : name
def write(File target, String templateName, def model) {
def tmpl = templateName.endsWith('.groovy') ? templateName + '.tmpl' : templateName
def body = template tmpl, model
new File(src, name).write(body)
target.write(body)
}
private void addTempFile(String group, File file) {

View File

@ -33,6 +33,11 @@ class ProjectRequest {
*/
static final DEFAULT_STARTER = 'root_starter'
/**
* The default name of the application class.
*/
static final String DEFAULT_APPLICATION_NAME = 'Application'
List<String> style = []
List<String> dependencies = []
String name
@ -43,6 +48,7 @@ class ProjectRequest {
String version
String bootVersion
String packaging
String applicationName
String language
String packageName
String javaVersion
@ -88,6 +94,11 @@ class ProjectRequest {
this.build = buildTag
}
}
if (!applicationName) {
this.applicationName = generateApplicationName(this.name, DEFAULT_APPLICATION_NAME)
}
afterResolution(metadata)
}
@ -114,7 +125,6 @@ class ProjectRequest {
root.id = DEFAULT_STARTER
root.asSpringBootStarter('')
resolvedDependencies << root
}
/**
@ -131,4 +141,51 @@ class ProjectRequest {
facets.contains(facet)
}
/**
* Generate a suitable application mame based on the specified name. If no suitable
* application name can be generated from the specified {@code name}, the
* {@code defaultApplicationName} is used instead.
* <p>No suitable application name can be generated if the name is {@code null} or
* if it contains an invalid character for a class identifier.
*/
static String generateApplicationName(String name, String defaultApplicationName) {
if (!name) {
return defaultApplicationName
}
String text = splitCamelCase(name.trim())
String result = text.replaceAll("(_|-| |:)+([A-Za-z0-9])", { Object[] it ->
it[2].toUpperCase()
})
if (!result.endsWith('Application')) {
result += 'Application'
}
String candidate = result.capitalize();
if (hasInvalidChar(candidate)) {
return defaultApplicationName
} else {
return candidate
}
}
private static String splitCamelCase(String text) {
text.split('(?<!(^|[A-Z]))(?=[A-Z])|(?<!^)(?=[A-Z][a-z])').collect {
String s = it.toLowerCase()
s.capitalize()
}.join("")
}
private static boolean hasInvalidChar(String text) {
if (!Character.isJavaIdentifierStart(text.charAt(0))) {
return true
}
if (text.length() > 1) {
for (int i = 1; i < text.length(); i++) {
if (!Character.isJavaIdentifierPart(text.charAt(i))) {
return true
}
}
}
return false
}
}

View File

@ -8,9 +8,9 @@ import org.springframework.context.annotation.Configuration
@Configuration
@ComponentScan
@EnableAutoConfiguration
class Application {
class ${applicationName} {
static void main(String[] args) {
SpringApplication.run Application, args
SpringApplication.run ${applicationName}, args
}
}

View File

@ -8,9 +8,9 @@ import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application {
public class ${applicationName} {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
SpringApplication.run(${applicationName}.class, args);
}
}

View File

@ -6,8 +6,8 @@ ${testImports}import org.springframework.boot.test.SpringApplicationConfiguratio
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner
@RunWith(SpringJUnit4ClassRunner)
@SpringApplicationConfiguration(classes = Application)
${testAnnotations}class ApplicationTests {
@SpringApplicationConfiguration(classes = ${applicationName})
${testAnnotations}class ${applicationName}Tests {
@Test
void contextLoads() {

View File

@ -6,8 +6,8 @@ ${testImports}import org.springframework.boot.test.SpringApplicationConfiguratio
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
${testAnnotations}public class ApplicationTests {
@SpringApplicationConfiguration(classes = ${applicationName}.class)
${testAnnotations}public class ${applicationName}Tests {
@Test
public void contextLoads() {

View File

@ -3,11 +3,11 @@ package ${packageName}
import org.springframework.boot.builder.SpringApplicationBuilder
import org.springframework.boot.context.web.SpringBootServletInitializer
class ServletInitializer extends SpringBootServletInitializer {
class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
application.sources(Application)
application.sources(${applicationName})
}
}

View File

@ -7,7 +7,7 @@ public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
return application.sources(${applicationName}.class);
}
}

View File

@ -20,7 +20,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>${packageName}.Application</start-class>
<start-class>${packageName}.${applicationName}</start-class>
<java.version>${javaVersion}</java.version>
</properties>

View File

@ -51,7 +51,7 @@ class ProjectGeneratorTests {
projectGenerator.listeners << listener
def request = createProjectRequest('web')
generateMavenPom(request).hasStartClass('demo.Application')
generateMavenPom(request).hasStartClass('demo.DemoApplication')
.hasNoRepository().hasSpringBootStarterDependency('web')
verify(listener, times(1)).onGeneratedProject(request)
}
@ -73,7 +73,7 @@ class ProjectGeneratorTests {
def request = createProjectRequest('web')
generateProject(request).isJavaProject().isMavenProject().pomAssert()
.hasStartClass('demo.Application').hasNoRepository().hasSpringBootStarterDependency('web')
.hasStartClass('demo.DemoApplication').hasNoRepository().hasSpringBootStarterDependency('web')
verify(listener, times(1)).onGeneratedProject(request)
}
@ -88,7 +88,7 @@ class ProjectGeneratorTests {
void mavenPomWithBootSnapshot() {
def request = createProjectRequest('web')
request.bootVersion = '1.0.1.BUILD-SNAPSHOT'
generateMavenPom(request).hasStartClass('demo.Application')
generateMavenPom(request).hasStartClass('demo.DemoApplication')
.hasSnapshotRepository().hasSpringBootStarterDependency('web')
}
@ -105,7 +105,7 @@ class ProjectGeneratorTests {
projectGenerator.metadata = metadata
def request = createProjectRequest('thymeleaf')
generateMavenPom(request).hasStartClass('demo.Application')
generateMavenPom(request).hasStartClass('demo.DemoApplication')
.hasDependency('org.foo', 'thymeleaf')
.hasDependenciesCount(2)
@ -125,7 +125,7 @@ class ProjectGeneratorTests {
def request = createProjectRequest('thymeleaf')
request.packaging = 'war'
generateMavenPom(request).hasStartClass('demo.Application')
generateMavenPom(request).hasStartClass('demo.DemoApplication')
.hasSpringBootStarterDependency('tomcat')
.hasDependency('org.foo', 'thymeleaf') // This is tagged as web facet so it brings the web one
.hasSpringBootStarterDependency('test')
@ -137,7 +137,7 @@ class ProjectGeneratorTests {
void mavenWarPomWithoutWebFacet() {
def request = createProjectRequest('data-jpa')
request.packaging = 'war'
generateMavenPom(request).hasStartClass('demo.Application')
generateMavenPom(request).hasStartClass('demo.DemoApplication')
.hasSpringBootStarterDependency('tomcat')
.hasSpringBootStarterDependency('data-jpa')
.hasSpringBootStarterDependency('web') // Added by war packaging

View File

@ -29,6 +29,8 @@ import static org.junit.Assert.assertNull
*/
class ProjectRequestTests {
private static final String DEFAULT_APPLICATION_NAME = 'FooBarApplication'
@Rule
public final ExpectedException thrown = ExpectedException.none()
@ -127,6 +129,105 @@ class ProjectRequestTests {
request.resolve(metadata)
}
@Test
void resolveApplicationNameWithNoName() {
def request = new ProjectRequest()
def metadata = InitializrMetadataBuilder.withDefaults().validateAndGet()
request.resolve(metadata)
assertEquals ProjectRequest.DEFAULT_APPLICATION_NAME, request.applicationName
}
@Test
void resolveApplicationName() {
def request = new ProjectRequest()
request.name = 'Foo2'
def metadata = InitializrMetadataBuilder.withDefaults().validateAndGet()
request.resolve(metadata)
assertEquals 'Foo2Application', request.applicationName
}
@Test
void resolveApplicationNameWithApplicationNameSet() {
def request = new ProjectRequest()
request.name = 'Foo2'
request.applicationName ='MyApplicationName'
def metadata = InitializrMetadataBuilder.withDefaults().validateAndGet()
request.resolve(metadata)
assertEquals 'MyApplicationName', request.applicationName
}
@Test
void generateApplicationNameSimple() {
assertEquals 'DemoApplication', generateApplicationName('demo')
}
@Test
void generateApplicationNameSimpleApplication() {
assertEquals 'DemoApplication', generateApplicationName('demoApplication')
}
@Test
void generateApplicationNameSimpleCamelCase() {
assertEquals 'MyDemoApplication', generateApplicationName('myDemo')
}
@Test
void generateApplicationNameSimpleUnderscore() {
assertEquals 'MyDemoApplication', generateApplicationName('my_demo')
}
@Test
void generateApplicationNameSimpleColon() {
assertEquals 'MyDemoApplication', generateApplicationName('my:demo')
}
@Test
void generateApplicationNameSimpleSpace() {
assertEquals 'MyDemoApplication', generateApplicationName('my demo')
}
@Test
void generateApplicationNamSsimpleDash() {
assertEquals 'MyDemoApplication', generateApplicationName('my-demo')
}
@Test
void generateApplicationNameUpperCaseUnderscore() {
assertEquals 'MyDemoApplication', generateApplicationName('MY_DEMO')
}
@Test
void generateApplicationNameUpperCaseDash() {
assertEquals 'MyDemoApplication', generateApplicationName('MY-DEMO')
}
@Test
void generateApplicationNameMultiSpaces() {
assertEquals 'MyDemoApplication', generateApplicationName(' my demo ')
}
@Test
void generateApplicationNameMultiSpacesUpperCase() {
assertEquals 'MyDemoApplication', generateApplicationName(' MY DEMO ')
}
@Test
void generateApplicationNameInvalidStartCharacter() {
assertEquals DEFAULT_APPLICATION_NAME, generateApplicationName('1MyDemo')
}
@Test
void generateApplicationNameInvalidPartCharacter() {
assertEquals DEFAULT_APPLICATION_NAME, generateApplicationName('MyDe|mo')
}
private static generateApplicationName(String text) {
ProjectRequest.generateApplicationName(text, DEFAULT_APPLICATION_NAME)
}
private static void assertBootStarter(InitializrMetadata.Dependency actual, String name) {
def expected = new InitializrMetadata.Dependency()
expected.asSpringBootStarter(name)

View File

@ -16,6 +16,8 @@
package io.spring.initializr.support
import io.spring.initializr.InitializrMetadata
import static org.junit.Assert.assertEquals
/**
@ -26,6 +28,8 @@ import static org.junit.Assert.assertEquals
*/
class ProjectAssert {
private static final DEFAULT_APPLICATION_NAME = generateDefaultApplicationName()
final File dir
/**
@ -51,17 +55,25 @@ class ProjectAssert {
hasFile('build.gradle').hasNoFile('pom.xml')
}
ProjectAssert isJavaProject() {
hasFile('src/main/java/demo/Application.java',
'src/test/java/demo/ApplicationTests.java',
ProjectAssert isJavaProject(String expectedApplicationName) {
hasFile("src/main/java/demo/${expectedApplicationName}.java",
"src/test/java/demo/${expectedApplicationName}Tests.java",
'src/main/resources/application.properties')
}
ProjectAssert isJavaWarProject() {
isJavaProject().hasStaticAndTemplatesResources(true)
ProjectAssert isJavaProject() {
isJavaProject(DEFAULT_APPLICATION_NAME)
}
ProjectAssert isJavaWarProject(String expectedApplicationName) {
isJavaProject(expectedApplicationName).hasStaticAndTemplatesResources(true)
.hasFile('src/main/java/demo/ServletInitializer.java')
}
ProjectAssert isJavaWarProject() {
isJavaWarProject(DEFAULT_APPLICATION_NAME)
}
ProjectAssert hasStaticAndTemplatesResources(boolean web) {
assertFile('src/main/resources/templates', web)
assertFile('src/main/resources/static', web)
@ -91,4 +103,8 @@ class ProjectAssert {
new File(dir, localPath)
}
private static generateDefaultApplicationName() {
InitializrMetadata.Defaults.DEFAULT_NAME.capitalize() + 'Application'
}
}

View File

@ -86,7 +86,8 @@ abstract class AbstractInitializerControllerFormIntegrationTests extends Abstrac
assertEquals 'attachment; filename="foo-bar.zip"', value
def projectAssert = zipProjectAssert(webResponse)
projectAssert.isMavenProject().isJavaProject().hasStaticAndTemplatesResources(true)
projectAssert.isMavenProject().isJavaProject('MyProjectApplication')
.hasStaticAndTemplatesResources(true)
projectAssert.pomAssert().hasGroupId('com.acme').hasArtifactId('foo-bar')
.hasName('My project').hasDescription('A description for my project')

View File

@ -20,7 +20,7 @@ class MainControllerDefaultsIntegrationTests extends AbstractInitializrControlle
def content = restTemplate.getForObject(createUrl('/pom.xml?style=web'), String)
def pomAssert = new PomAssert(content)
pomAssert.hasGroupId('org.foo').hasArtifactId('foo-bar').hasVersion('1.2.4-SNAPSHOT').hasPackaging('jar')
.hasName('FooBar').hasDescription('FooBar Project').hasStartClass('org.foo.demo.Application')
.hasName('FooBar').hasDescription('FooBar Project').hasStartClass('org.foo.demo.FooBarApplication')
}
@Test