Skip to content

Commit

Permalink
Prevent invalid application name
Browse files Browse the repository at this point in the history
Previously, if a user choose 'SpringBoot' or 'Spring' as the name of the
project, the service will generate a `SpringBootApplication` or
`SpringApplication` respectively. Both of which leads to a compilation
failure since those names are already used in the current context.

The generation of the application name based on the project's name have
been moved to InitializrMetadata and two new properties have been
introduced:

* env.fallbackApplicationName defines the name of the application if the
one that was generated was invalid for some reasons
* env.invalidApplicationNames defines a list of application names that
should flagged as invalid. When the current candidate is equal to one of
them, the fallback should be used instead

These properties have default values that prevent such issue to happen
by default.

Fixes spring-iogh-79
  • Loading branch information
snicoll committed Feb 21, 2015
1 parent 886110b commit 3e50bcc
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 127 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,7 @@ class CommandLineHelpGenerator {
defaults[it.key] = it.value
}
}
defaults['applicationName'] = ProjectRequest.generateApplicationName(metadata.defaults.name,
ProjectRequest.DEFAULT_APPLICATION_NAME)
defaults['applicationName'] = metadata.generateApplicationName(metadata.defaults.name)
defaults['baseDir'] = 'no base dir'
defaults['dependencies'] = 'none'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,34 @@ class InitializrMetadata {
"$defaults.bootVersion/spring-boot-cli-$defaults.bootVersion-bin.$extension"
}

/**
* Generate a suitable application mame based on the specified name. If no suitable
* application name can be generated from the specified {@code name}, the
* {@link Env#fallbackApplicationName} 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.
* @see Env#fallbackApplicationName
* @see Env#invalidApplicationNames
*/
String generateApplicationName(String name) {
if (!name) {
return env.fallbackApplicationName
}
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) || env.invalidApplicationNames.contains(candidate)) {
return env.fallbackApplicationName
} else {
return candidate
}
}

/**
* Initializes a {@link ProjectRequest} instance with the defaults
* defined in this instance.
Expand Down Expand Up @@ -223,6 +251,27 @@ class InitializrMetadata {
}
}

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
}

static class DependencyGroup {

String name
Expand Down Expand Up @@ -359,6 +408,17 @@ class InitializrMetadata {

String springBootMetadataUrl = 'https://spring.io/project_metadata/spring-boot'

/**
* The application name to use if none could be generated.
*/
String fallbackApplicationName = 'Application'

/**
* The list of invalid application names. If such name is chosen or generated,
* the {@link #fallbackApplicationName} should be used instead.
*/
List<String> invalidApplicationNames = ['SpringApplication', 'SpringBootApplication']

boolean forceSsl = true

void validate() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -35,11 +35,6 @@ 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
Expand Down Expand Up @@ -110,7 +105,7 @@ class ProjectRequest {
}

if (!applicationName) {
this.applicationName = generateApplicationName(this.name, DEFAULT_APPLICATION_NAME)
this.applicationName = metadata.generateApplicationName(this.name)
}

afterResolution(metadata)
Expand Down Expand Up @@ -155,51 +150,4 @@ 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
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,81 @@ class InitializrMetadataTests {
assertEquals 'two', InitializrMetadata.getDefault(elements)
}

@Test
void generateApplicationNameSimple() {
assertEquals 'DemoApplication', this.metadata.generateApplicationName('demo')
}

@Test
void generateApplicationNameSimpleApplication() {
assertEquals 'DemoApplication', this.metadata.generateApplicationName('demoApplication')
}

@Test
void generateApplicationNameSimpleCamelCase() {
assertEquals 'MyDemoApplication', this.metadata.generateApplicationName('myDemo')
}

@Test
void generateApplicationNameSimpleUnderscore() {
assertEquals 'MyDemoApplication', this.metadata.generateApplicationName('my_demo')
}

@Test
void generateApplicationNameSimpleColon() {
assertEquals 'MyDemoApplication', this.metadata.generateApplicationName('my:demo')
}

@Test
void generateApplicationNameSimpleSpace() {
assertEquals 'MyDemoApplication', this.metadata.generateApplicationName('my demo')
}

@Test
void generateApplicationNamSsimpleDash() {
assertEquals 'MyDemoApplication', this.metadata.generateApplicationName('my-demo')
}

@Test
void generateApplicationNameUpperCaseUnderscore() {
assertEquals 'MyDemoApplication', this.metadata.generateApplicationName('MY_DEMO')
}

@Test
void generateApplicationNameUpperCaseDash() {
assertEquals 'MyDemoApplication', this.metadata.generateApplicationName('MY-DEMO')
}

@Test
void generateApplicationNameMultiSpaces() {
assertEquals 'MyDemoApplication', this.metadata.generateApplicationName(' my demo ')
}

@Test
void generateApplicationNameMultiSpacesUpperCase() {
assertEquals 'MyDemoApplication', this.metadata.generateApplicationName(' MY DEMO ')
}

@Test
void generateApplicationNameInvalidStartCharacter() {
assertEquals this.metadata.env.fallbackApplicationName, this.metadata.generateApplicationName('1MyDemo')
}

@Test
void generateApplicationNameInvalidPartCharacter() {
assertEquals this.metadata.env.fallbackApplicationName, this.metadata.generateApplicationName('MyDe|mo')
}

@Test
void generateApplicationNameInvalidApplicationName() {
assertEquals this.metadata.env.fallbackApplicationName, this.metadata.generateApplicationName('SpringBoot')
}

@Test
void generateApplicationNameAnotherInvalidApplicationName() {
assertEquals this.metadata.env.fallbackApplicationName, this.metadata.generateApplicationName('Spring')
}

private static ProjectRequest doCreateProjectRequest(InitializrMetadata metadata) {
def request = new ProjectRequest()
metadata.initializeProjectRequest(request)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ class ProjectRequestTests {
def metadata = InitializrMetadataBuilder.withDefaults().validateAndGet()

request.resolve(metadata)
assertEquals ProjectRequest.DEFAULT_APPLICATION_NAME, request.applicationName
assertEquals metadata.env.fallbackApplicationName, request.applicationName
}

@Test
Expand All @@ -190,74 +190,6 @@ class ProjectRequestTests {
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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -44,4 +44,15 @@ class MainControllerEnvIntegrationTests extends AbstractInitializrControllerInte
assertTrue "Force SSL should be disabled", body.contains("http://localhost:$port/install.sh")
}

@Test
void generateProjectWithInvalidName() {
downloadZip('/starter.zip?style=data-jpa&name=Invalid')
.isJavaProject('FooBarApplication')
.isMavenProject()
.hasStaticAndTemplatesResources(false).pomAssert()
.hasDependenciesCount(2)
.hasSpringBootStarterDependency('data-jpa')
.hasSpringBootStarterDependency('test')
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
initializr:
env:
artifactRepository: https://repo.spring.io/lib-release
forceSsl: false
forceSsl: false
fallbackApplicationName: FooBarApplication
invalidApplicationNames:
- InvalidApplication

0 comments on commit 3e50bcc

Please sign in to comment.