diff --git a/.gitignore b/.gitignore
index 55ef266102f2..9fd7f8b7a6f3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -115,14 +115,14 @@ samples/client/petstore/swift/**/SwaggerClientTests/SwaggerClient.xcworkspace/xc
samples/client/petstore/swift/**/SwaggerClientTests/Pods/
#samples/client/petstore/swift/**/SwaggerClientTests/Pods/Pods.xcodeproj/xcuserdata
#samples/client/petstore/swift/**/SwaggerClientTests/Pods/Pods.xcodeproj/xcshareddata/xcschemes
-samples/client/petstore/swift/**/SwaggerClientTests/Podfile.lock
+samples/client/petstore/swift/**/SwaggerClientTests/Podfile.lock
# Swift3
samples/client/petstore/swift3/**/SwaggerClientTests/SwaggerClient.xcodeproj/xcuserdata
samples/client/petstore/swift3/**/SwaggerClientTests/SwaggerClient.xcworkspace/xcuserdata
#samples/client/petstore/swift3/**/SwaggerClientTests/Pods/
#samples/client/petstore/swift3/**/SwaggerClientTests/Pods/Pods.xcodeproj/xcuserdata
#samples/client/petstore/swift3/**/SwaggerClientTests/Pods/Pods.xcodeproj/xcshareddata/xcschemes
-samples/client/petstore/swift3/**/SwaggerClientTests/Podfile.lock
+samples/client/petstore/swift3/**/SwaggerClientTests/Podfile.lock
# C#
*.csproj.user
diff --git a/CI/pom.xml.circleci b/CI/pom.xml.circleci
index bf2d5323a0a3..1d50c1410d89 100644
--- a/CI/pom.xml.circleci
+++ b/CI/pom.xml.circleci
@@ -957,6 +957,7 @@
samples/server/petstore/scala-lagom-server
samples/server/petstore/scalatra
samples/server/petstore/finch
+ samples/server/petstore/kotlin-springboot
diff --git a/bin/kotlin-springboot-petstore-server.sh b/bin/kotlin-springboot-petstore-server.sh
new file mode 100755
index 000000000000..7e1c3888c8f0
--- /dev/null
+++ b/bin/kotlin-springboot-petstore-server.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+SCRIPT="$0"
+echo "# START SCRIPT: $SCRIPT"
+
+while [ -h "$SCRIPT" ] ; do
+ ls=$(ls -ld "$SCRIPT")
+ link=$(expr "$ls" : '.*-> \(.*\)$')
+ if expr "$link" : '/.*' > /dev/null; then
+ SCRIPT="$link"
+ else
+ SCRIPT=$(dirname "$SCRIPT")/"$link"
+ fi
+done
+
+if [ ! -d "${APP_DIR}" ]; then
+ APP_DIR=$(dirname "$SCRIPT")/..
+ APP_DIR=$(cd "${APP_DIR}"; pwd)
+fi
+
+executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar"
+
+if [ ! -f "$executable" ]
+then
+ mvn clean package
+fi
+
+export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
+ags="$@ generate -i modules/openapi-generator/src/test/resources/2_0/petstore.yaml -t modules/openapi-generator/src/main/resources/kotlin-spring -g kotlin-spring -o samples/server/petstore/kotlin-springboot --additional-properties=library=spring-boot,beanValidations=true,swaggerAnnotations=true,serviceImplementation=true"
+
+echo "Cleaning previously generated files if any from samples/server/petstore/kotlin-springboot"
+rm -rf samples/server/petstore/kotlin-springboot
+
+echo "Generating Kotling Spring Boot server..."
+java $JAVA_OPTS -jar $executable $ags
diff --git a/bin/openapi3/kotlin-springboot-petstore-server.sh b/bin/openapi3/kotlin-springboot-petstore-server.sh
new file mode 100755
index 000000000000..3ac7b6f68eb0
--- /dev/null
+++ b/bin/openapi3/kotlin-springboot-petstore-server.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+SCRIPT="$0"
+echo "# START SCRIPT: $SCRIPT"
+
+while [ -h "$SCRIPT" ] ; do
+ ls=$(ls -ld "$SCRIPT")
+ link=$(expr "$ls" : '.*-> \(.*\)$')
+ if expr "$link" : '/.*' > /dev/null; then
+ SCRIPT="$link"
+ else
+ SCRIPT=$(dirname "$SCRIPT")/"$link"
+ fi
+done
+
+if [ ! -d "${APP_DIR}" ]; then
+ APP_DIR=$(dirname "$SCRIPT")/..
+ APP_DIR=$(cd "${APP_DIR}"; pwd)
+fi
+
+executable="./modules/openapi-generator-cli/target/openapi-generator-cli.jar"
+
+if [ ! -f "$executable" ]
+then
+ mvn clean package
+fi
+
+export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
+ags="$@ generate -i modules/openapi-generator/src/test/resources/3_0/petstore.yaml -t modules/openapi-generator/src/main/resources/kotlin-spring -g kotlin-spring -o samples/server/openapi3/petstore/kotlin-springboot --additional-properties=library=spring-boot,beanValidations=true,swaggerAnnotations=true,serviceImplementation=true"
+
+echo "Cleaning previously generated files if any from samples/server/openapi3/petstore/kotlin-springboot"
+rm -rf samples/server/openapi3/petstore/kotlin-springboot
+
+echo "Generating Kotling Spring Boot server..."
+java $JAVA_OPTS -jar $executable $ags
diff --git a/bin/windows/kotlin-springboot-petstore-server.bat b/bin/windows/kotlin-springboot-petstore-server.bat
new file mode 100644
index 000000000000..6b7e1ad6e771
--- /dev/null
+++ b/bin/windows/kotlin-springboot-petstore-server.bat
@@ -0,0 +1,10 @@
+set executable=.\modules\openapi-generator-cli\target\openapi-generator-cli.jar
+
+If Not Exist %executable% (
+ mvn clean package
+)
+
+REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M
+set ags=generate -i modules\openapi-generator\src\test\resources\2_0\petstore.yaml -g kotlin-spring -o samples\server\petstore\kotlin-springboot --additional-properties=library=spring-boot
+
+java %JAVA_OPTS% -jar %executable% %ags%
diff --git a/bin/windows/openapi3/kotlin-springboot-petstore-server.bat b/bin/windows/openapi3/kotlin-springboot-petstore-server.bat
new file mode 100644
index 000000000000..c86efaee3c81
--- /dev/null
+++ b/bin/windows/openapi3/kotlin-springboot-petstore-server.bat
@@ -0,0 +1,10 @@
+set executable=.\modules\openapi-generator-cli\target\openapi-generator-cli.jar
+
+If Not Exist %executable% (
+ mvn clean package
+)
+
+REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M
+set ags=generate -i modules\openapi-generator\src\test\resources\3_0\petstore.yaml -g kotlin-spring -o samples\server\openapi3\petstore\kotlin-springboot --additional-properties=library=spring-boot
+
+java %JAVA_OPTS% -jar %executable% %ags%
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java
index 774c96fe33fa..7ec6bc1573ff 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java
@@ -19,6 +19,7 @@
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Schema;
+import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.CliOption;
import org.openapitools.codegen.CodegenConfig;
import org.openapitools.codegen.CodegenConstants;
@@ -28,11 +29,7 @@
import org.slf4j.LoggerFactory;
import java.io.File;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Map;
+import java.util.*;
public abstract class AbstractKotlinCodegen extends DefaultCodegen implements CodegenConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractKotlinCodegen.class);
@@ -55,6 +52,7 @@ public AbstractKotlinCodegen() {
languageSpecificPrimitives = new HashSet(Arrays.asList(
"kotlin.Byte",
+ "kotlin.ByteArray",
"kotlin.Short",
"kotlin.Int",
"kotlin.Long",
@@ -139,6 +137,7 @@ public AbstractKotlinCodegen() {
defaultIncludes = new HashSet(Arrays.asList(
"kotlin.Byte",
+ "kotlin.ByteArray",
"kotlin.Short",
"kotlin.Int",
"kotlin.Long",
@@ -159,21 +158,22 @@ public AbstractKotlinCodegen() {
typeMapping.put("float", "kotlin.Float");
typeMapping.put("long", "kotlin.Long");
typeMapping.put("double", "kotlin.Double");
+ typeMapping.put("ByteArray", "kotlin.ByteArray");
typeMapping.put("number", "java.math.BigDecimal");
typeMapping.put("date-time", "java.time.LocalDateTime");
typeMapping.put("date", "java.time.LocalDateTime");
typeMapping.put("file", "java.io.File");
typeMapping.put("array", "kotlin.Array");
- typeMapping.put("list", "kotlin.Array");
+ typeMapping.put("list", "kotlin.collections.List");
typeMapping.put("map", "kotlin.collections.Map");
typeMapping.put("object", "kotlin.Any");
typeMapping.put("binary", "kotlin.Array");
typeMapping.put("Date", "java.time.LocalDateTime");
typeMapping.put("DateTime", "java.time.LocalDateTime");
- instantiationTypes.put("array", "arrayOf");
- instantiationTypes.put("list", "arrayOf");
- instantiationTypes.put("map", "mapOf");
+ instantiationTypes.put("array", "kotlin.arrayOf");
+ instantiationTypes.put("list", "kotlin.arrayOf");
+ instantiationTypes.put("map", "kotlin.mapOf");
importMapping = new HashMap();
importMapping.put("BigDecimal", "java.math.BigDecimal");
@@ -473,13 +473,53 @@ public String toModelName(final String name) {
// Camelize name of nested properties
modifiedName = camelize(modifiedName);
- if (reservedWords.contains(modifiedName)) {
- modifiedName = escapeReservedWord(modifiedName);
+ // model name cannot use reserved keyword, e.g. return
+ if (isReservedWord(modifiedName)) {
+ final String modelName = "Model" + modifiedName;
+ LOGGER.warn(modifiedName + " (reserved word) cannot be used as model name. Renamed to " + modelName);
+ return modelName;
+ }
+
+ // model name starts with number
+ if (modifiedName.matches("^\\d.*")) {
+ final String modelName = "Model" + modifiedName; // e.g. 200Response => Model200Response (after camelize)
+ LOGGER.warn(name + " (model name starts with number) cannot be used as model name. Renamed to " + modelName);
+ return modelName;
}
return titleCase(modifiedName);
}
+ /**
+ * Return the operation ID (method name)
+ *
+ * @param operationId operation ID
+ * @return the sanitized method name
+ */
+ @Override
+ public String toOperationId(String operationId) {
+ // throw exception if method name is empty
+ if (StringUtils.isEmpty(operationId))
+ throw new RuntimeException("Empty method/operation name (operationId) not allowed");
+
+ operationId = camelize(sanitizeName(operationId), true);
+
+ // method name cannot use reserved keyword, e.g. return
+ if (isReservedWord(operationId)) {
+ String newOperationId = camelize("call_" + operationId, true);
+ LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + newOperationId);
+ return newOperationId;
+ }
+
+ // operationId starts with a number
+ if (operationId.matches("^\\d.*")) {
+ LOGGER.warn(operationId + " (starting with a number) cannot be used as method sname. Renamed to " + camelize("call_" + operationId), true);
+ operationId = camelize("call_" + operationId, true);
+ }
+
+ return operationId;
+ }
+
@Override
public String toModelFilename(String name) {
// Should be the same as the model name
@@ -577,4 +617,70 @@ public String toEnumValue(String value, String datatype) {
public boolean isDataTypeString(final String dataType) {
return "String".equals(dataType) || "kotlin.String".equals(dataType);
}
+
+ @Override
+ public String toParamName(String name) {
+ // to avoid conflicts with 'callback' parameter for async call
+ if ("callback".equals(name)) {
+ return "paramCallback";
+ }
+
+ // should be the same as variable name
+ return toVarName(name);
+ }
+
+ @Override
+ public String toVarName(String name) {
+ // sanitize name
+ name = sanitizeName(name, "\\W-[\\$]");
+
+ if (name.toLowerCase(Locale.ROOT).matches("^_*class$")) {
+ return "propertyClass";
+ }
+
+ if ("_".equals(name)) {
+ name = "_u";
+ }
+
+ // if it's all uppper case, do nothing
+ if (name.matches("^[A-Z_]*$")) {
+ return name;
+ }
+
+ if (startsWithTwoUppercaseLetters(name)) {
+ name = name.substring(0, 2).toLowerCase(Locale.ROOT) + name.substring(2);
+ }
+
+ // If name contains special chars -> replace them.
+ if ((name.chars().anyMatch(character -> specialCharReplacements.keySet().contains("" + ((char) character))))) {
+ List allowedCharacters = new ArrayList<>();
+ allowedCharacters.add("_");
+ allowedCharacters.add("$");
+ name = escapeSpecialCharacters(name, allowedCharacters, "_");
+ }
+
+ // camelize (lower first character) the variable name
+ // pet_id => petId
+ name = camelize(name, true);
+
+ // for reserved word or word starting with number or containing dollar symbol, escape it
+ if (isReservedWord(name) || name.matches("(^\\d.*)|(.*[$].*)")) {
+ name = escapeReservedWord(name);
+ }
+
+ return name;
+ }
+
+ @Override
+ public String toRegularExpression(String pattern) {
+ return escapeText(pattern);
+ }
+
+ private boolean startsWithTwoUppercaseLetters(String name) {
+ boolean startsWithTwoUppercaseLetters = false;
+ if (name.length() > 1) {
+ startsWithTwoUppercaseLetters = name.substring(0, 2).equals(name.substring(0, 2).toUpperCase(Locale.ROOT));
+ }
+ return startsWithTwoUppercaseLetters;
+ }
}
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java
new file mode 100644
index 000000000000..99fc5a186066
--- /dev/null
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java
@@ -0,0 +1,485 @@
+package org.openapitools.codegen.languages;
+
+import com.google.common.collect.ImmutableMap;
+import com.samskivert.mustache.Mustache;
+import com.samskivert.mustache.Template;
+import io.swagger.v3.oas.models.OpenAPI;
+import org.openapitools.codegen.*;
+import org.openapitools.codegen.languages.features.BeanValidationFeatures;
+import org.openapitools.codegen.utils.URLPathUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Writer;
+import java.net.URL;
+import java.util.*;
+import java.util.regex.Matcher;
+
+
+public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
+ implements BeanValidationFeatures {
+
+ private static Logger LOGGER =
+ LoggerFactory.getLogger(KotlinSpringServerCodegen.class);
+
+ private static final HashSet VARIABLE_RESERVED_WORDS =
+ new HashSet(Arrays.asList(
+ "ApiClient",
+ "ApiException",
+ "ApiResponse"
+ ));
+
+ public static final String TITLE = "title";
+ public static final String LAMBDA = "lambda";
+ public static final String SERVER_PORT = "serverPort";
+ public static final String BASE_PACKAGE = "basePackage";
+ public static final String SPRING_BOOT = "spring-boot";
+ public static final String EXCEPTION_HANDLER = "exceptionHandler";
+ public static final String GRADLE_BUILD_FILE = "gradleBuildFile";
+ public static final String SWAGGER_ANNOTATIONS = "swaggerAnnotations";
+ public static final String SERVICE_INTERFACE = "serviceInterface";
+ public static final String SERVICE_IMPLEMENTATION = "serviceImplementation";
+
+ private String basePackage;
+ private String serverPort = "8080";
+ private String title = "OpenAPI Kotlin Spring";
+ private String resourceFolder = "src/main/resources";
+ private boolean useBeanValidation = true;
+ private boolean exceptionHandler = true;
+ private boolean gradleBuildFile = true;
+ private boolean swaggerAnnotations = false;
+ private boolean serviceInterface = false;
+ private boolean serviceImplementation = false;
+
+ public KotlinSpringServerCodegen() {
+ super();
+
+ reservedWords.addAll(VARIABLE_RESERVED_WORDS);
+
+ outputFolder = "generated-code/kotlin-spring";
+ apiTestTemplateFiles.clear(); // TODO: add test template
+ embeddedTemplateDir = templateDir = "kotlin-spring";
+
+ artifactId = "openapi-spring";
+ basePackage = "org.openapitools";
+ apiPackage = "org.openapitools.api";
+ modelPackage = "org.openapitools.model";
+
+ addOption(TITLE, "server title name or client service name", title);
+ addOption(BASE_PACKAGE, "base package for generated code", basePackage);
+ addOption(SERVER_PORT, "configuration the port in which the sever is to run on", serverPort);
+ addOption(CodegenConstants.MODEL_PACKAGE, "model package for generated code", modelPackage);
+ addOption(CodegenConstants.API_PACKAGE, "api package for generated code", apiPackage);
+ addSwitch(EXCEPTION_HANDLER, "generate default global exception handlers", exceptionHandler);
+ addSwitch(GRADLE_BUILD_FILE, "generate a gradle build file using the Kotlin DSL", gradleBuildFile);
+ addSwitch(SWAGGER_ANNOTATIONS, "generate swagger annotations to go alongside controllers and models", swaggerAnnotations);
+ addSwitch(SERVICE_INTERFACE, "generate service interfaces to go alongside controllers. In most " +
+ "cases this option would be used to update an existing project, so not to override implementations. " +
+ "Useful to help facilitate the generation gap pattern", serviceInterface);
+ addSwitch(SERVICE_IMPLEMENTATION, "generate stub service implementations that extends service " +
+ "interfaces. If this is set to true service interfaces will also be generated", serviceImplementation);
+ addSwitch(USE_BEANVALIDATION, "Use BeanValidation API annotations to validate data types", useBeanValidation);
+
+ supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application.");
+ setLibrary(SPRING_BOOT);
+
+ CliOption cliOpt = new CliOption(CodegenConstants.LIBRARY, "library template (sub-template) to use");
+ cliOpt.setDefault(SPRING_BOOT);
+ cliOpt.setEnum(supportedLibraries);
+ cliOptions.add(cliOpt);
+ }
+
+ public String getResourceFolder() {
+ return this.resourceFolder;
+ }
+
+ public void setResourceFolder(String resourceFolder) {
+ this.resourceFolder = resourceFolder;
+ }
+
+ public String getBasePackage() {
+ return this.basePackage;
+ }
+
+ public void setBasePackage(String basePackage) {
+ this.basePackage = basePackage;
+ }
+
+ public String getServerPort() {
+ return this.serverPort;
+ }
+
+ public void setServerPort(String serverPort) {
+ this.serverPort = serverPort;
+ }
+
+ public boolean getExceptionHandler() {
+ return this.exceptionHandler;
+ }
+
+ public void setExceptionHandler(boolean exceptionHandler) {
+ this.exceptionHandler = exceptionHandler;
+ }
+
+ public boolean getGradleBuildFile() {
+ return this.gradleBuildFile;
+ }
+
+ public void setGradleBuildFile(boolean gradleBuildFile) {
+ this.gradleBuildFile = gradleBuildFile;
+ }
+
+ public boolean getSwaggerAnnotations() {
+ return this.swaggerAnnotations;
+ }
+
+ public void setSwaggerAnnotations(boolean swaggerAnnotations) {
+ this.swaggerAnnotations = swaggerAnnotations;
+ }
+
+ public boolean getServiceInterface() {
+ return this.serviceInterface;
+ }
+
+ public void setServiceInterface(boolean serviceInterface) {
+ this.serviceInterface = serviceInterface;
+ }
+
+ public boolean getServiceImplementation() {
+ return this.serviceImplementation;
+ }
+
+ public void setServiceImplementation(boolean serviceImplementation) {
+ this.serviceImplementation = serviceImplementation;
+ }
+
+ public boolean getUseBeanValidation() {
+ return this.useBeanValidation;
+ }
+
+ @Override
+ public void setUseBeanValidation(boolean useBeanValidation) {
+ this.useBeanValidation = useBeanValidation;
+ }
+
+ @Override
+ public CodegenType getTag() {
+ return CodegenType.SERVER;
+ }
+
+ @Override
+ public String getName() {
+ return "kotlin-spring";
+ }
+
+ @Override
+ public String getHelp() {
+ return "Generates a Kotlin Spring application.";
+ }
+
+ @Override
+ public void processOpts() {
+ super.processOpts();
+
+ typeMapping.put("date", "java.time.LocalDate");
+ typeMapping.put("date-time", "java.time.OffsetDateTime");
+ typeMapping.put("Date", "java.time.LocalDate");
+ typeMapping.put("DateTime", "java.time.OffsetDateTime");
+
+ importMapping.put("Date", "java.time.LocalDate");
+ importMapping.put("DateTime", "java.time.OffsetDateTime");
+
+ // optional jackson mappings for BigDecimal support
+ importMapping.put("ToStringSerializer", "com.fasterxml.jackson.databind.ser.std.ToStringSerializer");
+ importMapping.put("JsonSerialize", "com.fasterxml.jackson.databind.annotation.JsonSerialize");
+
+ // Swagger import mappings
+ importMapping.put("ApiModel", "io.swagger.annotations.ApiModel");
+ importMapping.put("ApiModelProperty", "io.swagger.annotations.ApiModelProperty");
+
+ // Jackson import mappings
+ importMapping.put("JsonValue", "com.fasterxml.jackson.annotation.JsonValue");
+ importMapping.put("JsonCreator", "com.fasterxml.jackson.annotation.JsonCreator");
+ importMapping.put("JsonProperty", "com.fasterxml.jackson.annotation.JsonProperty");
+ importMapping.put("JsonSubTypes", "com.fasterxml.jackson.annotation.JsonSubTypes");
+ importMapping.put("JsonTypeInfo", "com.fasterxml.jackson.annotation.JsonTypeInfo");
+ // import JsonCreator if JsonProperty is imported
+ // used later in recursive import in postProcessingModels
+ importMapping.put("com.fasterxml.jackson.annotation.JsonProperty", "com.fasterxml.jackson.annotation.JsonCreator");
+
+ // TODO when adding invokerPackage
+ //importMapping.put("StringUtil", invokerPackage + ".StringUtil");
+
+ if (!additionalProperties.containsKey(CodegenConstants.LIBRARY)) {
+ additionalProperties.put(CodegenConstants.LIBRARY, library);
+ }
+
+ if (additionalProperties.containsKey(BASE_PACKAGE)) {
+ this.setBasePackage((String) additionalProperties.get(BASE_PACKAGE));
+ } else {
+ additionalProperties.put(BASE_PACKAGE, basePackage);
+ }
+
+ if (additionalProperties.containsKey(SERVER_PORT)) {
+ this.setServerPort((String) additionalProperties.get(SERVER_PORT));
+ } else {
+ additionalProperties.put(SERVER_PORT, serverPort);
+ }
+
+ if (additionalProperties.containsKey(EXCEPTION_HANDLER)) {
+ this.setExceptionHandler(Boolean.valueOf(additionalProperties.get(EXCEPTION_HANDLER).toString()));
+ }
+ writePropertyBack(EXCEPTION_HANDLER, exceptionHandler);
+
+ if (additionalProperties.containsKey(GRADLE_BUILD_FILE)) {
+ this.setGradleBuildFile(Boolean.valueOf(additionalProperties.get(GRADLE_BUILD_FILE).toString()));
+ }
+ writePropertyBack(GRADLE_BUILD_FILE, gradleBuildFile);
+
+ if (additionalProperties.containsKey(SWAGGER_ANNOTATIONS)) {
+ this.setSwaggerAnnotations(Boolean.valueOf(additionalProperties.get(SWAGGER_ANNOTATIONS).toString()));
+ }
+ writePropertyBack(SWAGGER_ANNOTATIONS, swaggerAnnotations);
+
+ if (additionalProperties.containsKey(SERVICE_INTERFACE)) {
+ this.setServiceInterface(Boolean.valueOf(additionalProperties.get(SERVICE_INTERFACE).toString()));
+ }
+ writePropertyBack(SERVICE_INTERFACE, serviceInterface);
+
+ if (additionalProperties.containsKey(SERVICE_IMPLEMENTATION)) {
+ this.setServiceImplementation(Boolean.valueOf(additionalProperties.get(SERVICE_IMPLEMENTATION).toString()));
+ }
+ writePropertyBack(SERVICE_IMPLEMENTATION, serviceImplementation);
+
+ if (additionalProperties.containsKey(USE_BEANVALIDATION)) {
+ this.setUseBeanValidation(convertPropertyToBoolean(USE_BEANVALIDATION));
+ }
+ writePropertyBack(USE_BEANVALIDATION, useBeanValidation);
+
+ modelTemplateFiles.put("model.mustache", ".kt");
+ apiTemplateFiles.put("api.mustache", ".kt");
+ supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
+
+ if (this.serviceInterface) {
+ apiTemplateFiles.put("service.mustache", "Service.kt");
+ } else if (this.serviceImplementation) {
+ LOGGER.warn("If you set `serviceImplementation` to true, `serviceInterface` will also be set to true");
+ additionalProperties.put(SERVICE_INTERFACE, true);
+ apiTemplateFiles.put("service.mustache", "Service.kt");
+ apiTemplateFiles.put("serviceImpl.mustache", "ServiceImpl.kt");
+ }
+
+ if (this.exceptionHandler) {
+ supportingFiles.add(new SupportingFile("exceptions.mustache",
+ sanitizeDirectory(sourceFolder + File.separator + apiPackage), "Exceptions.kt"));
+ }
+
+ if (library.equals(SPRING_BOOT)) {
+ LOGGER.info("Setup code generator for Kotlin Spring Boot");
+ supportingFiles.add(new SupportingFile("pom.mustache", "", "pom.xml"));
+
+ if (this.gradleBuildFile) {
+ supportingFiles.add(new SupportingFile("buildGradleKts.mustache", "", "build.gradle.kts"));
+ supportingFiles.add(new SupportingFile("settingsGradle.mustache", "", "settings.gradle"));
+ }
+
+ supportingFiles.add(new SupportingFile("application.mustache", resourceFolder, "application.yaml"));
+ supportingFiles.add(new SupportingFile("springBootApplication.mustache",
+ sanitizeDirectory(sourceFolder + File.separator + basePackage), "Application.kt"));
+ }
+
+ addMustacheLambdas(additionalProperties);
+
+ // spring uses the jackson lib, and we disallow configuration.
+ additionalProperties.put("jackson", "true");
+ }
+
+ private void addMustacheLambdas(final Map objs) {
+ Map lambdas =
+ new ImmutableMap.Builder()
+ .put("escapeDoubleQuote", new EscapeLambda("\"", "\\\""))
+ .build();
+
+ if (objs.containsKey(LAMBDA)) {
+ LOGGER.warn("The lambda property is a reserved word, and will be overwritten!");
+ }
+ objs.put(LAMBDA, lambdas);
+ }
+
+ @Override
+ public void preprocessOpenAPI(OpenAPI openAPI) {
+ super.preprocessOpenAPI(openAPI);
+
+ if (!additionalProperties.containsKey(TITLE)) {
+ // The purpose of the title is for:
+ // - README documentation
+ // - The spring.application.name
+ // - And linking the @RequestMapping
+ // This is an additional step we add when pre-processing the API spec, if
+ // there is no user configuration set for the `title` of the project,
+ // we try build and normalise a title from the API spec itself.
+ String title = openAPI.getInfo().getTitle();
+
+ // Drop any API suffix
+ if (title != null) {
+ title = title.trim().replace(" ", "-");
+ if (title.toUpperCase(Locale.ROOT).endsWith("API"))
+ title = title.substring(0, title.length() - 3);
+
+ this.title = camelize(sanitizeName(title), true);
+ }
+ additionalProperties.put(TITLE, this.title);
+ }
+
+ if (!additionalProperties.containsKey(SERVER_PORT)) {
+ URL url = URLPathUtils.getServerURL(openAPI);
+ this.additionalProperties.put(SERVER_PORT, URLPathUtils.getPort(url, 8080));
+ }
+
+ // TODO: Handle tags
+ }
+
+ @Override
+ public void postProcessModelProperty(CodegenModel model, CodegenProperty property) {
+ super.postProcessModelProperty(model, property);
+
+ if ("null".equals(property.example)) {
+ property.example = null;
+ }
+
+ //Add imports for Jackson
+ if (!Boolean.TRUE.equals(model.isEnum)) {
+ model.imports.add("JsonProperty");
+ if (Boolean.TRUE.equals(model.hasEnums)) {
+ model.imports.add("JsonValue");
+ }
+
+ } else {
+ //Needed imports for Jackson's JsonCreator
+ if (additionalProperties.containsKey("jackson")) {
+ model.imports.add("JsonCreator");
+ }
+ }
+
+ }
+
+ @Override
+ public Map postProcessModelsEnum(Map objs) {
+ objs = super.postProcessModelsEnum(objs);
+
+ //Add imports for Jackson
+ List