All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kathra.codegen.languages.KathraPythonCodegen Maven / Gradle / Ivy

There is a newer version: 1.2.0
Show newest version
/* 
 * Copyright 2019 The Kathra 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.
 *
 * Contributors:
 *
 *    IRT SystemX (https://www.kathra.org/)    
 *
 */

package org.kathra.codegen.languages;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import org.kathra.codegen.*;
import io.swagger.models.*;
import io.swagger.models.parameters.*;
import io.swagger.models.properties.*;
import io.swagger.util.Yaml;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class KathraPythonCodegen extends DefaultCodegen implements CodegenConfig {

    public static final String MODEL_MUSTACHE = "model.mustache";

    public static final String REPOSITORY_URI = "repositoryUrl";
    public static final String REPOSITORY_PYTHON_NAME = "repositoryPythonName";
    public static final String GROUP_ID = "groupId";
    public static final String ARTIFACT_ID = "artifactId";
    public static final String ARTIFACT_VERSION = "artifactVersion";
    public static final String OBJECTS = "objects";
    public static final String X_GROUP_ID = "x-groupId";
    public static final String X_MODEL_PACKAGE = "x-modelPackage";
    public static final String SERVICE_MUSTACHE = "service.mustache";
    public static final String LAUNCHER_MUSTACHE = "launcher.mustache";
    public static final String INIT_MUSTACHE = "__init__.mustache";

    public static final String SOURCE_FOLDER_ATTRIBUTE_NAME = "sourceFolder";
    public static final String SUPPORT_PYTHON2 = "supportPython2";
    public static final String SWAGGER_MUSTACHE = "swagger.mustache";
    public static final String SWAGGER_YAML = "swagger.yaml";

    protected int serverPort = 8080;
    protected String packageName;
    protected String packageVersion;
    protected String packagePrefix = "";
    protected Map regexModifiers;
    protected String apiName;
    protected String apiSuffix;
    protected String invokerPackage = "org.kathra";
    protected String projectFolder;
    protected String sourceFolder;

    protected String repositoryUrl = "undefined";

    protected String repositoryPythonName = "undefined";

    protected String groupId = "org.kathra";
    protected String artifactId = "kathra-autogenerated-artifact";
    protected String artifactVersion = "1.0.0-SNAPSHOT";


    protected String artifactVersionApi = "1.0.0-SNAPSHOT";

    protected String interfaceModule = "";
    protected String modelModule = "";
    protected String implModule = "";
    protected String moduleName = "";
    protected String modulePrefix = "";

    HashMap kathraImports = new HashMap();

    public KathraPythonCodegen() {
        super();
        modelPackage = "models";
        testPackage = "test";

        languageSpecificPrimitives.clear();
        languageSpecificPrimitives.add("int");
        languageSpecificPrimitives.add("float");
        languageSpecificPrimitives.add("List");
        languageSpecificPrimitives.add("Dict");
        languageSpecificPrimitives.add("bool");
        languageSpecificPrimitives.add("str");
        languageSpecificPrimitives.add("datetime");
        languageSpecificPrimitives.add("date");
        languageSpecificPrimitives.add("file");
        languageSpecificPrimitives.add("object");

        typeMapping.clear();
        typeMapping.put("integer", "int");
        typeMapping.put("float", "float");
        typeMapping.put("number", "float");
        typeMapping.put("long", "int");
        typeMapping.put("double", "float");
        typeMapping.put("array", "List");
        typeMapping.put("map", "Dict");
        typeMapping.put("boolean", "bool");
        typeMapping.put("string", "str");
        typeMapping.put("date", "date");
        typeMapping.put("DateTime", "datetime");
        typeMapping.put("object", "object");
        typeMapping.put("file", "file");
        typeMapping.put("UUID", "str");

        // from https://docs.python.org/3/reference/lexical_analysis.html#keywords
        setReservedWordsLowerCase(
                Arrays.asList(
                        // @property
                        // python reserved words
                        "and", "del", "from", "not", "while", "as", "elif", "global", "or", "with",
                        "assert", "else", "if", "pass", "yield", "break", "except", "import",
                        "print", "class", "exec", "in", "raise", "continue", "finally", "is",
                        "return", "def", "for", "lambda", "try", "self", "None", "True", "False", "nonlocal"));

        // set the output folder here
        outputFolder = "generated-code" + File.separator + "python";

        //apiTestTemplateFiles().put(API_TEST_MUSTACHE, ".py");

        /*
         * Additional Properties.  These values can be passed to the templates and
         * are available in models, apis, and supporting files
         */
        additionalProperties.put("serverPort", serverPort);

        /*
         * Supporting Files.  You can write single files for the generator with the
         * entire object tree available.  If the input file has a suffix of `.mustache
         * it will be processed by the template engine.  Otherwise, it will be copied
         */
        //supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
        supportingFiles.add(new SupportingFile("setup.mustache", "", "setup.py"));
        supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore"));
        //supportingFiles.add(new SupportingFile("MANIFEST.in.mustache", "", "MANIFEST.in"));

        regexModifiers = new HashMap();
        regexModifiers.put('i', "IGNORECASE");
        regexModifiers.put('l', "LOCALE");
        regexModifiers.put('m', "MULTILINE");
        regexModifiers.put('s', "DOTALL");
        regexModifiers.put('u', "UNICODE");
        regexModifiers.put('x', "VERBOSE");

        supportedLibraries.put(KathraLibraryType.MODEL.getName(), "Generate Model");
        supportedLibraries.put(KathraLibraryType.INTERFACE.getName(), "Generate Interface + Api");
        supportedLibraries.put(KathraLibraryType.IMPLEM.getName(), "Generate Implementation");
        supportedLibraries.put(KathraLibraryType.CLIENT.getName(), "Generate Client");

        CliOption libraryOption = new CliOption(CodegenConstants.LIBRARY, "library template (sub-template) to use");
        libraryOption.setEnum(supportedLibraries);
        // set model as the default
        libraryOption.setDefault(KathraLibraryType.MODEL.getName());
        cliOptions.add(libraryOption);
        setLibrary(KathraLibraryType.MODEL.getName());
    }

    @Override
    public void processOpts() {

        if (additionalProperties.containsKey(CodegenConstants.TEMPLATE_DIR)) {
            this.setTemplateDir((String) additionalProperties.get(CodegenConstants.TEMPLATE_DIR));
        }

        if (additionalProperties.containsKey(CodegenConstants.MODEL_PACKAGE)) {
            this.setModelPackage((String) additionalProperties.get(CodegenConstants.MODEL_PACKAGE));
        }

        if (additionalProperties.containsKey(CodegenConstants.API_PACKAGE)) {
            this.setApiPackage((String) additionalProperties.get(CodegenConstants.API_PACKAGE));
        }

        if (additionalProperties.containsKey(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG)) {
            this.setSortParamsByRequiredFlag(Boolean.valueOf(additionalProperties
                    .get(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG).toString()));
        }

        if (additionalProperties.containsKey(CodegenConstants.ENSURE_UNIQUE_PARAMS)) {
            this.setEnsureUniqueParams(Boolean.valueOf(additionalProperties
                    .get(CodegenConstants.ENSURE_UNIQUE_PARAMS).toString()));
        }

        if (additionalProperties.containsKey(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS)) {
            this.setAllowUnicodeIdentifiers(Boolean.valueOf(additionalProperties
                    .get(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS).toString()));
        }

        if (additionalProperties.containsKey(CodegenConstants.MODEL_NAME_PREFIX)) {
            this.setModelNamePrefix((String) additionalProperties.get(CodegenConstants.MODEL_NAME_PREFIX));
        }

        if (additionalProperties.containsKey(CodegenConstants.MODEL_NAME_SUFFIX)) {
            this.setModelNameSuffix((String) additionalProperties.get(CodegenConstants.MODEL_NAME_SUFFIX));
        }

        if (additionalProperties.containsKey(CodegenConstants.REMOVE_OPERATION_ID_PREFIX)) {
            this.setSortParamsByRequiredFlag(Boolean.valueOf(additionalProperties
                    .get(CodegenConstants.REMOVE_OPERATION_ID_PREFIX).toString()));
        }

        if ((additionalProperties.get(CodegenConstants.ARTIFACT_ID) != null) && (!additionalProperties.get(CodegenConstants.ARTIFACT_ID).toString().trim().isEmpty()))
            artifactId = additionalProperties.get(CodegenConstants.ARTIFACT_ID).toString().trim();
        else
            additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId);

        if ((additionalProperties.get(CodegenConstants.MODEL_PACKAGE) != null) && (!additionalProperties.get(CodegenConstants.MODEL_PACKAGE).toString().trim().isEmpty()))
            modelPackage = additionalProperties.get(CodegenConstants.MODEL_PACKAGE).toString().trim();
        else
            additionalProperties.put(CodegenConstants.MODEL_PACKAGE, modelPackage);

        if ((additionalProperties.get(CodegenConstants.API_PACKAGE) != null) && (!additionalProperties.get(CodegenConstants.API_PACKAGE).toString().trim().isEmpty()))
            apiPackage = additionalProperties.get(CodegenConstants.API_PACKAGE).toString().trim();
        else
            additionalProperties.put(CodegenConstants.API_PACKAGE, apiPackage);

        if ((additionalProperties.get(CodegenConstants.INVOKER_PACKAGE) != null) && (!additionalProperties.get(CodegenConstants.INVOKER_PACKAGE).toString().trim().isEmpty()))
            invokerPackage = additionalProperties.get(CodegenConstants.INVOKER_PACKAGE).toString().trim();
        else
            additionalProperties.put(CodegenConstants.INVOKER_PACKAGE, invokerPackage);
        invokerPackage = invokerPackage.toLowerCase();

        if ((additionalProperties.get(CodegenConstants.REPOSITORY_URL) != null) && (!additionalProperties.get(CodegenConstants.REPOSITORY_URL).toString().trim().isEmpty()))
            repositoryUrl = additionalProperties.get(CodegenConstants.REPOSITORY_URL).toString().trim();
        else
            additionalProperties.put(CodegenConstants.REPOSITORY_URL, repositoryUrl);

        if ((additionalProperties.get(CodegenConstants.REPOSITORY_PYTHON_NAME) != null) && (!additionalProperties.get(CodegenConstants.REPOSITORY_PYTHON_NAME).toString().trim().isEmpty()))
            repositoryPythonName = additionalProperties.get(CodegenConstants.REPOSITORY_PYTHON_NAME).toString().trim();
        else
            additionalProperties.put(CodegenConstants.REPOSITORY_PYTHON_NAME, repositoryPythonName);


        if ((additionalProperties.get(CodegenConstants.GROUP_ID) != null) && (!additionalProperties.get(CodegenConstants.GROUP_ID).toString().trim().isEmpty()))
            groupId = additionalProperties.get(CodegenConstants.GROUP_ID).toString().trim();
        else
            additionalProperties.put(CodegenConstants.GROUP_ID, groupId);
        groupId = groupId.toLowerCase();

        if ((additionalProperties.get(CodegenConstants.ARTIFACT_VERSION) != null) && (!additionalProperties.get(CodegenConstants.ARTIFACT_VERSION).toString().trim().isEmpty()))
            artifactVersion = additionalProperties.get(CodegenConstants.ARTIFACT_VERSION).toString().trim();
        else
            additionalProperties.put(CodegenConstants.ARTIFACT_VERSION, artifactVersion);

        if (additionalProperties.containsKey(CodegenConstants.ARTIFACT_VERSION_API)) {
            artifactVersionApi = additionalProperties.get(CodegenConstants.ARTIFACT_VERSION_API).toString();
        }

        if ((additionalProperties.get(CodegenConstants.MODEL_NAME_PREFIX) != null) && (!additionalProperties.get(CodegenConstants.MODEL_NAME_PREFIX).toString().trim().isEmpty()))
            modelNamePrefix = additionalProperties.get(CodegenConstants.MODEL_NAME_PREFIX).toString().trim();
        else
            additionalProperties.put(CodegenConstants.MODEL_NAME_PREFIX, modelNamePrefix);

        if ((additionalProperties.get(CodegenConstants.MODEL_NAME_SUFFIX) != null) && (!additionalProperties.get(CodegenConstants.MODEL_NAME_SUFFIX).toString().trim().isEmpty()))
            modelNameSuffix = additionalProperties.get(CodegenConstants.MODEL_NAME_SUFFIX).toString().trim();
        else
            additionalProperties.put(CodegenConstants.MODEL_NAME_SUFFIX, modelNameSuffix);

        if ((additionalProperties.get(CodegenConstants.GIT_USER_ID) != null) && (!additionalProperties.get(CodegenConstants.GIT_USER_ID).toString().trim().isEmpty()))
            gitUserId = additionalProperties.get(CodegenConstants.GIT_USER_ID).toString().trim();
        else
            additionalProperties.put(CodegenConstants.GIT_USER_ID, gitUserId);

        if ((additionalProperties.get(CodegenConstants.GIT_REPO_ID) != null) && (!additionalProperties.get(CodegenConstants.GIT_REPO_ID).toString().trim().isEmpty()))
            gitRepoId = additionalProperties.get(CodegenConstants.GIT_REPO_ID).toString().trim();
        else
            additionalProperties.put(CodegenConstants.GIT_REPO_ID, gitRepoId);

        if ((additionalProperties.get(CodegenConstants.RELEASE_NOTE) != null) && (!additionalProperties.get(CodegenConstants.RELEASE_NOTE).toString().trim().isEmpty()))
            releaseNote = additionalProperties.get(CodegenConstants.RELEASE_NOTE).toString().trim();
        else
            additionalProperties.put(CodegenConstants.RELEASE_NOTE, gitRepoId);

        if ((additionalProperties.get(CodegenConstants.HTTP_USER_AGENT) != null) && (!additionalProperties.get(CodegenConstants.HTTP_USER_AGENT).toString().trim().isEmpty()))
            httpUserAgent = additionalProperties.get(CodegenConstants.HTTP_USER_AGENT).toString().trim();
        else
            additionalProperties.put(CodegenConstants.HTTP_USER_AGENT, gitRepoId);

        if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) {
            setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME));
        }

        if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) {
            setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION));
        } else {
            setPackageVersion("1.0.0-SNAPSHOT");
            additionalProperties.put(CodegenConstants.PACKAGE_VERSION, this.packageVersion);
        }

        if (Boolean.TRUE.equals(additionalProperties.get(SUPPORT_PYTHON2))) {
            additionalProperties.put(SUPPORT_PYTHON2, Boolean.TRUE);
            typeMapping.put("long", "long");
        }

        intializeAdditionalProperties();
    }

    private void setArtifactIdApi(String artifactVersionApi) {
        this.artifactVersionApi = artifactVersionApi;
    }

    public void intializeAdditionalProperties() {
        additionalProperties.put("artifactName", generateArtifactName(artifactId));
        packageName = artifactId = artifactId.toLowerCase();
        additionalProperties.put("artifactId", artifactId);
        packageVersion = artifactVersion;
        moduleName = sourceFolder = projectFolder = artifactId.replace('-', '_');
        modulePrefix = projectFolder.substring(0, projectFolder.lastIndexOf('_') + 1);
        packagePrefix = modulePrefix.replace('_', '-');
        apiName = StringUtils.capitalize(apiPackage);
        additionalProperties.put("apiName", apiName);
        modelPackage = packagePrefix + KathraLibraryType.MODEL.getName().toLowerCase();
        modelModule = modelPackage.replace('-', '_');

        /*
         * Template Location.  This is the location templates will be read from.
         * The generator will use the resource stream to attempt to read the templates.
         */
        embeddedTemplateDir = templateDir = "Kathra/Python/" + getLibrary();

        if (KathraLibraryType.MODEL.getName().equalsIgnoreCase(getLibrary())) {
            apiSuffix = "Model";
            modelTemplateFiles.put(MODEL_MUSTACHE, ".py");
        } else if (KathraLibraryType.INTERFACE.getName().equalsIgnoreCase(getLibrary())) {
            apiTemplateFiles.put(SERVICE_MUSTACHE, ".py");
            apiSuffix = "Service";
        } else if (KathraLibraryType.IMPLEM.getName().equalsIgnoreCase(getLibrary())) {
            apiSuffix = "Controller";
            apiTemplateFiles.put("controller.mustache", ".py");
            supportingFiles.add(new SupportingFile(SWAGGER_MUSTACHE, "", SWAGGER_YAML));
            supportingFiles.add(new SupportingFile(LAUNCHER_MUSTACHE, "", "launcher.py"));
            supportingFiles.add(new SupportingFile("Dockerfile.mustache", "", "Dockerfile"));
            supportingFiles.add(new SupportingFile("Pipfile.mustache", "", "Pipfile"));
            supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
            interfaceModule = (modulePrefix + KathraLibraryType.INTERFACE.getName() + "." + underscore(apiPackage) + "_service").toLowerCase();
        } else if (KathraLibraryType.CLIENT.getName().equalsIgnoreCase(getLibrary())) {
            apiSuffix = "Controller";
        }

        supportingFiles.add(new SupportingFile(INIT_MUSTACHE, sourceFolder, "__init__.py"));
        supportingFiles.add(new SupportingFile("requirements.mustache", sourceFolder, "requirements.txt"));

        //The other library (implementation, client, interface) must import packages and classes of model
        //The implementation library must import packages and classes of model and interface
        //The implementation class must import its own classes

        additionalProperties.put(SOURCE_FOLDER_ATTRIBUTE_NAME, sourceFolder);
        additionalProperties.put("packageName", packageName);
        additionalProperties.put("packageVersion", packageVersion);
        additionalProperties.put("moduleName", moduleName);
        additionalProperties.put("modulePrefix", modulePrefix);
        additionalProperties.put("packagePrefix", packagePrefix);
        additionalProperties.put("modelPackage", modelPackage);
        additionalProperties.put("modelModule", modelModule);
        additionalProperties.put("interfaceModule", interfaceModule);
        additionalProperties.put("className", apiName + apiSuffix);
        additionalProperties.put("classFileName", underscore(apiName + apiSuffix));
    }

    private String generateArtifactName(String artifactId) {
        String[] parts = artifactId.split("\\-");
        StringBuilder f = new StringBuilder();
        for (int i = 0; i < parts.length; i++) {
            String z = parts[i];
            if (z.length() > 0) {
                if (i == 0) {
                    f.append(z.toUpperCase());
                } else {
                    f.append(StringUtils.capitalize(z));
                }

                if (i < parts.length - 1) {
                    f.append(" :: ");
                }
            }
        }
        return f.toString();
    }

    @Override
    public void preprocessSwagger(Swagger swagger) {
        super.preprocessSwagger(swagger);

        if (swagger.getInfo().getTitle() == null || swagger.getInfo().getTitle().isEmpty()) {
            swagger.getInfo().setTitle(apiName);
        }

        handleKathraOperationId(swagger);

        handleKathraDependencies(swagger);

        handleKathraTags(swagger);
    }

    private void handleKathraOperationId(Swagger swagger) {
        // need vendor extensions for x-swagger-router-controller
        Map paths = swagger.getPaths();
        if (paths != null) {
            for (String pathname : paths.keySet()) {
                Path path = paths.get(pathname);
                Map operationMap = path.getOperationMap();
                if (operationMap != null) {
                    for (HttpMethod method : operationMap.keySet()) {
                        Operation operation = operationMap.get(method);
                        String tag = apiName;
                        if (operation.getTags() != null && operation.getTags().size() > 0) {
                            tag = operation.getTags().get(0);
                        }
                        String operationId = operation.getOperationId();
                        if (operationId == null) {
                            operationId = getOrGenerateOperationId(operation, pathname, method.toString());
                        }
                        operation.setOperationId(toOperationId(operationId));

                        for (Parameter param : operation.getParameters()) {
                            // sanitize the param name but don't underscore it since it's used for request mapping
                            String name = param.getName();
                            String paramName = sanitizeName(name);
                            if (!paramName.equals(name)) {
                                LOGGER.warn(name + " cannot be used as parameter name with flask-connexion and was sanitized as " + paramName);
                            }
                            param.setName(paramName);
                        }
                    }
                }
            }
        }
    }

    private void handleKathraTags(Swagger swagger) {
        if (KathraLibraryType.IMPLEM.getName().equalsIgnoreCase(getLibrary()) || KathraLibraryType.INTERFACE.getName().equalsIgnoreCase(getLibrary()) || KathraLibraryType.CLIENT.getName().equalsIgnoreCase(getLibrary())) {
            for (Path p : swagger.getPaths().values()) {
                for (Operation op : p.getOperations()) {
                    if (op.getTags() == null || op.getTags().isEmpty()) {
                        op.addTag(apiName);
                    }
                }
            }
        }
    }

    private void handleKathraDependencies(Swagger swagger) {
        Map definitions = swagger.getDefinitions();

        if (definitions == null) definitions = new HashMap();

        HashMap dependencies = new HashMap();

        List artifactDependencies = parseKathraDependencies(swagger.getVendorExtensions());
        additionalProperties.put("artifactDependencies", artifactDependencies);

        for (Iterator> iterator = definitions.entrySet().iterator(); iterator.hasNext(); ) {
            Map.Entry modelEntry = iterator.next();
            Model model = modelEntry.getValue();
            String modelName = modelEntry.getKey();

            // Handling external models from other Api definitions
            if (model.getProperties() == null && !(model instanceof ComposedModel)) {
                dependencies.putAll(handleExternalModel(modelName, model.getVendorExtensions(), artifactDependencies));
                continue;
            }

            // Handling autogenerated model from same Api definition
            if (!KathraLibraryType.MODEL.getName().equalsIgnoreCase(getLibrary())) {
                handleInternalModel(dependencies, modelName);
            }
        }

        if (KathraLibraryType.MODEL.getName().equalsIgnoreCase(getLibrary())) {
            for (Map.Entry modelEntry : definitions.entrySet()) {
                Model model = modelEntry.getValue();
                String modelName = modelEntry.getKey();
                processModelDescription(modelName, model);
                processModelProperties(dependencies, model);
            }
            for (HashMap dep : dependencies.values()) {
                if (!((List) dep.get(OBJECTS)).isEmpty()) {
                    addArtifactDependency(artifactDependencies, dep);
                }
            }
        } else {
            for (HashMap dep : dependencies.values()) {
                addArtifactDependency(artifactDependencies, dep);
            }
            if (KathraLibraryType.IMPLEM.getName().equalsIgnoreCase(getLibrary())) {
                HashMap dep = new HashMap();
                dep.put(ARTIFACT_ID, packagePrefix + KathraLibraryType.INTERFACE.getName());
                dep.put(ARTIFACT_VERSION, artifactVersionApi);
                addArtifactDependency(artifactDependencies, dep);
            }
        }
    }

    private List parseKathraDependencies(Map vendorExtensions) {
        if (vendorExtensions != null && !vendorExtensions.isEmpty() && vendorExtensions.containsKey("x-dependencies")) {
            return (List) vendorExtensions.get("x-dependencies");
        }
        return new ArrayList();
    }

    private void processModelProperties(HashMap dependencies, Model model) {
        if (model.getProperties() != null) {
            for (Map.Entry propertyEntry : model.getProperties().entrySet()) {
                Property property = propertyEntry.getValue();
                String refName = null;

                if (property instanceof RefProperty) refName = ((RefProperty) property).getSimpleRef();
                else if (property instanceof RefModel) refName = ((RefModel) property).getSimpleRef();

                countDependency(dependencies, refName);
            }
        } else if (model instanceof ComposedModel) {
            ComposedModel cmodel = (ComposedModel) model;
            processModelProperties(dependencies, cmodel.getChild());
            countDependency(dependencies, cmodel.getInterfaces().get(0).getSimpleRef());
        }
    }

    private void countDependency(HashMap dependencies, String refName) {
        if (refName != null && kathraImports.containsKey(refName)) {
            HashMap dep = dependencies.get(kathraImports.get(refName));
            if (!((ArrayList) dep.get(OBJECTS)).contains(refName)) {
                ((ArrayList) dep.get(OBJECTS)).add(refName);
            }
        }
    }

    private void processModelDescription(String modelName, Model model) {
        if (model.getDescription() == null || model.getDescription().isEmpty()) {
            model.setDescription(modelName);
        }
        if (model.getProperties() != null) {
            for (Map.Entry propertyEntry : model.getProperties().entrySet()) {
                Property property = propertyEntry.getValue();
                if (property.getDescription() == null || property.getDescription().isEmpty()) {
                    property.setDescription(propertyEntry.getKey());
                }
            }
        } else if (model instanceof ComposedModel) {
            ComposedModel cmodel = (ComposedModel) model;
            cmodel.setParent(cmodel.getInterfaces().get(0));
            if (cmodel.getChild() == null) {
                cmodel.setChild(new ModelImpl().type("object"));
            }
            ModelImpl modelImpl = (ModelImpl) cmodel.getChild();
            processModelDescription(modelName, modelImpl);
        }
    }

    private void handleInternalModel(HashMap dependencies, String modelName) {
        kathraImports.put(modelName, modelModule + "." + underscore(modelName));
        if (!dependencies.containsKey(kathraImports.get(modelName))) {
            HashMap dep = new HashMap();
            dep.put(ARTIFACT_ID, modelPackage);
            dep.put(ARTIFACT_VERSION, artifactVersionApi);
            dependencies.put(kathraImports.get(modelName), dep);
        }
    }

    @Override
    public String toModelImport(String name) {
        String modelImport = "from ";

        if (KathraLibraryType.MODEL.getName().equalsIgnoreCase(getLibrary())) {
            modelImport += modelModule + "." + underscore(name);
        } else {
            if (kathraImports.containsKey(name)) {
                modelImport += kathraImports.get(name);
            } else {
                return invokerPackage + "." + apiPackage.toLowerCase() + "." + name;
            }
        }

        return modelImport + " import " + name;
    }

    private Map handleExternalModel(String modelName, Map vendorExtensions, List artifactDependencies) {
        HashMap dependencies = new HashMap();
        String artifactId = (String) vendorExtensions.get("x-artifactId");
        String groupId = (String) vendorExtensions.get(X_GROUP_ID);
        String modelPackage = (String) vendorExtensions.get(X_MODEL_PACKAGE);
        String artifactVersion = (String) vendorExtensions.get("x-artifactVersion");
        if (vendorExtensions.isEmpty() || artifactId == null) {
            return dependencies;
        } else {
            Map dependency = null;
            for (Map dep : artifactDependencies) {
                if (dep.get(ARTIFACT_ID).equals(artifactId)) {
                    dependency = dep;
                    break;
                }
            }
            if (dependency != null) {
                if (groupId == null) groupId = (String) dependency.get(GROUP_ID);
                if (artifactVersion == null) artifactVersion = (String) dependency.get(ARTIFACT_VERSION);
                if (modelPackage == null) modelPackage = (String) dependency.get("modelPackage");
                kathraImports.put(modelName, underscore(artifactId) +"."+ underscore(modelName));
                if (!dependencies.containsKey(kathraImports.get(modelName))) {
                    HashMap dep = new HashMap();
                    dep.put(GROUP_ID, groupId);
                    dep.put(ARTIFACT_ID, artifactId);
                    dep.put("modelPackage", modelPackage);
                    dep.put(ARTIFACT_VERSION, artifactVersion);
                    if (KathraLibraryType.MODEL.getName().equalsIgnoreCase(getLibrary()))
                        dep.put(OBJECTS, new ArrayList());
                    dependencies.put(kathraImports.get(modelName), dep);
                }
            }
        }
        return dependencies;
    }

    private void addArtifactDependency(List artifactDependencies, HashMap dep) {
        if (!artifactDependencies.contains(dep)) artifactDependencies.add(dep);
    }

    private static String dropDots(String str) {
        return str.replaceAll("\\.", "_");
    }

    /**
     * Configures the type of generator.
     *
     * @return the CodegenType for this generator
     * @see CodegenType
     */
    @Override
    public CodegenType getTag() {
        return CodegenType.SERVER;
    }

    /**
     * Configures a friendly name for the generator.  This will be used by the generator
     * to select the library with the -l flag.
     *
     * @return the friendly name for the generator
     */
    @Override
    public String getName() {
        return "KathraPython";
    }

    /**
     * Returns human-friendly help for the generator.  Provide the consumer with help
     * tips, parameters here
     *
     * @return A string value for the help message
     */
    @Override
    public String getHelp() {
        return "Generates a KathraPython";
    }

    @Override
    public String toApiName(String name) {
        if (name.length() == 0) {
            return initialCaps(apiName) + apiSuffix;
        }
        return initialCaps(name) + apiSuffix;
    }

    /**
     * Escapes a reserved word as defined in the `reservedWords` array. Handle escaping
     * those terms here.  This logic is only called if a variable matches the reserved words
     *
     * @return the escaped term
     */
    @Override
    public String escapeReservedWord(String name) {
        if (this.reservedWordsMappings().containsKey(name)) {
            return this.reservedWordsMappings().get(name);
        }
        return "_" + name; // add an underscore to the name
    }

    public String kathraApiFileFolder() {
        return (outputFolder() + File.separatorChar + apiFolder() + File.separatorChar).toLowerCase();
    }

    @Override
    public String apiFileFolder() {
        return kathraApiFileFolder().toLowerCase();
    }

    public String apiFolder() {
        return sourceFolder;
    }

    @Override
    public String modelFileFolder() {
        return (outputFolder() + "/" + sourceFolder);
    }

    @Override
    public String getTypeDeclaration(Property p) {
        if (p instanceof ArrayProperty) {
            ArrayProperty ap = (ArrayProperty) p;
            Property inner = ap.getItems();
            return getSwaggerType(p) + "[" + getTypeDeclaration(inner) + "]";
        } else if (p instanceof MapProperty) {
            MapProperty mp = (MapProperty) p;
            Property inner = mp.getAdditionalProperties();

            return getSwaggerType(p) + "[str, " + getTypeDeclaration(inner) + "]";
        }
        return super.getTypeDeclaration(p);
    }

    @Override
    public String getSwaggerType(Property p) {
        String swaggerType = super.getSwaggerType(p);
        String type = null;
        if (typeMapping.containsKey(swaggerType)) {
            type = typeMapping.get(swaggerType);
            if (languageSpecificPrimitives.contains(type)) {
                return type;
            }
        } else {
            type = toModelName(swaggerType);
        }
        return type;
    }

    @SuppressWarnings("unchecked")
    private static List> getOperations(Map objs) {
        List> result = new ArrayList>();
        Map apiInfo = (Map) objs.get("apiInfo");
        List> apis = (List>) apiInfo.get("apis");
        for (Map api : apis) {
            result.add((Map) api.get("operations"));
        }
        return result;
    }

    private static List> sortOperationsByPath(List ops) {
        Multimap opsByPath = ArrayListMultimap.create();

        for (CodegenOperation op : ops) {
            opsByPath.put(op.path, op);
        }

        List> opsByPathList = new ArrayList>();
        for (Map.Entry> entry : opsByPath.asMap().entrySet()) {
            Map opsByPathEntry = new HashMap();
            opsByPathList.add(opsByPathEntry);
            opsByPathEntry.put("path", entry.getKey());
            opsByPathEntry.put("operation", entry.getValue());
            List operationsForThisPath = Lists.newArrayList(entry.getValue());
            operationsForThisPath.get(operationsForThisPath.size() - 1).hasMore = false;
            if (opsByPathList.size() < opsByPath.asMap().size()) {
                opsByPathEntry.put("hasMore", "true");
            }
        }

        return opsByPathList;
    }

    @Override
    public Map postProcessSupportingFileData(Map objs) {
        Swagger swagger = (Swagger) objs.get("swagger");
        if (swagger != null) {
            if (KathraLibraryType.IMPLEM.getName().equalsIgnoreCase(getLibrary()))
                rectifySwagger(swagger);

            try {
                objs.put("swagger-yaml", Yaml.pretty().writeValueAsString(swagger));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        for (Map operations : getOperations(objs)) {
            @SuppressWarnings("unchecked")
            List ops = (List) operations.get("operation");

            List> opsByPathList = sortOperationsByPath(ops);
            operations.put("operationsByPath", opsByPathList);
        }
        return super.postProcessSupportingFileData(objs);
    }

    private void rectifySwagger(Swagger swagger) {
        if (swagger == null)
            return;
        Map paths = swagger.getPaths();
        if (paths == null)
            return;
        for (String resourcePath : paths.keySet()) {
            Path path = paths.get(resourcePath);

            List operations = path.getOperations();
            if (operations == null)
                return;
            int n = operations.size();
            for (int i = 0; i < n; ++i) {

                Operation currentOperation = operations.get(i);
                List currentTags = currentOperation.getTags();
                String currentTag = apiName;
                if ((currentTags != null) && (!currentTags.isEmpty()))
                    currentTag = currentTags.get(0);
                //At this moment, we consider that each operation in swagger file has only ONE tag at MOST
                String currentOperationId = currentOperation.getOperationId();
                if (!currentOperationId.contains(sourceFolder.toLowerCase()))
                    currentOperation.setOperationId("launcher.implem." + currentOperationId);
            }
        }
    }

    @Override
    public String toVarName(String name) {
        // sanitize name
        name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.

        // remove dollar sign
        name = name.replaceAll("$", "");

        // if it's all uppper case, convert to lower case
        if (name.matches("^[A-Z_]*$")) {
            name = name.toLowerCase();
        }

        // underscore the variable name
        // petId => pet_id
        name = underscore(name);

        // remove leading underscore
        name = name.replaceAll("^_*", "");

        // for reserved word or word starting with number, append _
        if (isReservedWord(name) || name.matches("^\\d.*")) {
            name = escapeReservedWord(name);
        }

        return name;
    }

    @Override
    public String toParamName(String name) {
        // don't do name =removeNonNameElementToCamelCase(name); // this breaks connexion, which does not modify param names before sending them
        if (reservedWords.contains(name)) {
            return escapeReservedWord(name);
        }
        // Param name is already sanitized in swagger spec processing
        return name;
    }

    @Override
    public String toModelFilename(String name) {
        // underscore the model file name
        // PhoneNumber => phone_number
        return underscore(dropDots(toModelName(name)));
    }

    @Override
    public String toApiFilename(String name) {
        return underscore(dropDots(toApiName(name)));
    }

    @Override
    public String toModelName(String name) {
        name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
        // remove dollar sign
        name = name.replaceAll("$", "");

        // model name cannot use reserved keyword, e.g. return
        if (isReservedWord(name)) {
            LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + camelize("model_" + name));
            name = "model_" + name; // e.g. return => ModelReturn (after camelize)
        }

        // model name starts with number
        if (name.matches("^\\d.*")) {
            LOGGER.warn(name + " (model name starts with number) cannot be used as model name. Renamed to " + camelize("model_" + name));
            name = "model_" + name; // e.g. 200Response => Model200Response (after camelize)
        }

        if (!StringUtils.isEmpty(modelNamePrefix)) {
            name = modelNamePrefix + "_" + name;
        }

        if (!StringUtils.isEmpty(modelNameSuffix)) {
            name = name + "_" + modelNameSuffix;
        }

        // camelize the model name
        // phone_number => PhoneNumber
        return name;
    }

    @Override
    public String toOperationId(String operationId) {
        operationId = super.toOperationId(operationId); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
        // Use the part after the last dot, e.g.
        //     controllers.defaultController.addPet => addPet
        operationId = operationId.replaceAll(".*\\.", "");
        // Need to underscore it since it has been processed via removeNonNameElementToCamelCase, e.g.
        //     addPet => add_pet
        return underscore(operationId);
    }

    /**
     * Return the default value of the property
     *
     * @param p Swagger property object
     * @return string presentation of the default value of the property
     */
    @Override
    public String toDefaultValue(Property p) {
        if (p instanceof StringProperty) {
            StringProperty dp = (StringProperty) p;
            if (dp.getDefault() != null) {
                return "'" + dp.getDefault() + "'";
            }
        } else if (p instanceof BooleanProperty) {
            BooleanProperty dp = (BooleanProperty) p;
            if (dp.getDefault() != null) {
                if (dp.getDefault().toString().equalsIgnoreCase("false"))
                    return "False";
                else
                    return "True";
            }
        } else if (p instanceof DateProperty) {
            // TODO
        } else if (p instanceof DateTimeProperty) {
            // TODO
        } else if (p instanceof DoubleProperty) {
            DoubleProperty dp = (DoubleProperty) p;
            if (dp.getDefault() != null) {
                return dp.getDefault().toString();
            }
        } else if (p instanceof FloatProperty) {
            FloatProperty dp = (FloatProperty) p;
            if (dp.getDefault() != null) {
                return dp.getDefault().toString();
            }
        } else if (p instanceof IntegerProperty) {
            IntegerProperty dp = (IntegerProperty) p;
            if (dp.getDefault() != null) {
                return dp.getDefault().toString();
            }
        } else if (p instanceof LongProperty) {
            LongProperty dp = (LongProperty) p;
            if (dp.getDefault() != null) {
                return dp.getDefault().toString();
            }
        }

        return null;
    }

    @Override
    public void setParameterExampleValue(CodegenParameter p) {
        String example;

        if (p.defaultValue == null) {
            example = p.example;
        } else {
            example = p.defaultValue;
        }

        String type = p.baseType;
        if (type == null) {
            type = p.dataType;
        }

        if ("String".equalsIgnoreCase(type) || "str".equalsIgnoreCase(type)) {
            if (example == null) {
                example = p.paramName + "_example";
            }
            example = "'" + escapeText(example) + "'";
        } else if ("Integer".equals(type) || "int".equals(type)) {
            if (p.minimum != null) {
                example = "" + (Integer.valueOf(p.minimum) + 1);
            }
            if (p.maximum != null) {
                example = "" + p.maximum;
            } else if (example == null) {
                example = "56";
            }
        } else if ("Long".equalsIgnoreCase(type)) {
            if (p.minimum != null) {
                example = "" + (Long.valueOf(p.minimum) + 1);
            }
            if (p.maximum != null) {
                example = "" + p.maximum;
            } else if (example == null) {
                example = "789";
            }
        } else if ("Float".equalsIgnoreCase(type) || "Double".equalsIgnoreCase(type)) {
            if (p.minimum != null) {
                example = "" + p.minimum;
            } else if (p.maximum != null) {
                example = "" + p.maximum;
            } else if (example == null) {
                example = "3.4";
            }
        } else if ("BOOLEAN".equalsIgnoreCase(type) || "bool".equalsIgnoreCase(type)) {
            if (example == null) {
                example = "True";
            }
        } else if ("file".equalsIgnoreCase(type)) {
            example = "(BytesIO(b'some file data'), 'file.txt')";
        } else if ("Date".equalsIgnoreCase(type)) {
            if (example == null) {
                example = "2013-10-20";
            }
            example = "'" + escapeText(example) + "'";
        } else if ("DateTime".equalsIgnoreCase(type)) {
            if (example == null) {
                example = "2013-10-20T19:20:30+01:00";
            }
            example = "'" + escapeText(example) + "'";
        } else if (!languageSpecificPrimitives.contains(type)) {
            // type is a model class, e.g. User
            example = type + "()";
        } else {
            LOGGER.warn("Type " + type + " not handled properly in setParameterExampleValue");
        }

        if (p.items != null && p.items.defaultValue != null) {
            example = p.items.defaultValue;
        }
        if (example == null) {
            example = "None";
        } else if (Boolean.TRUE.equals(p.isListContainer)) {
            if (Boolean.TRUE.equals(p.isBodyParam)) {
                example = "[" + example + "]";
            }
        } else if (Boolean.TRUE.equals(p.isMapContainer)) {
            example = "{'key': " + example + "}";
        }

        p.example = example;
    }

    public void setPackageName(String packageName) {
        this.packageName = packageName;
    }

    public void setPackageVersion(String packageVersion) {
        this.packageVersion = packageVersion;
    }

    @Override
    public String escapeQuotationMark(String input) {
        // remove ' to avoid code injection
        return input.replace("'", "");
    }

    @Override
    public String escapeUnsafeCharacters(String input) {
        // remove multiline comment
        return input.replace("'''", "'_'_'");
    }

    @Override
    public void postProcessModelProperty(CodegenModel model, CodegenProperty property) {
        if (StringUtils.isNotEmpty(property.pattern)) {
            addImport(model, "import re");
        }
        postProcessPattern(property.pattern, property.vendorExtensions);
    }

    @Override
    public void postProcessParameter(CodegenParameter parameter) {
        postProcessPattern(parameter.pattern, parameter.vendorExtensions);
    }

    /*
     * The swagger pattern spec follows the Perl convention and style of modifiers. Python
     * does not support this in as natural a way so it needs to convert it. See
     * https://docs.python.org/2/howto/regex.html#compilation-flags for details.
     */
    public void postProcessPattern(String pattern, Map vendorExtensions) {
        if (pattern != null) {
            int i = pattern.lastIndexOf('/');

            //Must follow Perl /pattern/modifiers convention
            if (pattern.charAt(0) != '/' || i < 2) {
                throw new IllegalArgumentException("Pattern must follow the Perl "
                        + "/pattern/modifiers convention. " + pattern + " is not valid.");
            }

            String regex = pattern.substring(1, i).replace("'", "\\'");
            List modifiers = new ArrayList();

            for (char c : pattern.substring(i).toCharArray()) {
                if (regexModifiers.containsKey(c)) {
                    String modifier = regexModifiers.get(c);
                    modifiers.add(modifier);
                }
            }

            vendorExtensions.put("x-regex", regex);
            vendorExtensions.put("x-modifiers", modifiers);
        }
    }

    /**
     * Convert Swagger Operation object to Codegen Operation object (without providing a Swagger object)
     *
     * @param path        the path of the operation
     * @param httpMethod  HTTP method
     * @param operation   Swagger operation object
     * @param definitions a map of Swagger models
     * @return Codegen Operation object
     */
    public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, Map definitions) {
        return fromOperation(path, httpMethod, operation, definitions, null);
    }

    /**
     * Convert Swagger Operation object to Codegen Operation object
     *
     * @param path        the path of the operation
     * @param httpMethod  HTTP method
     * @param operation   Swagger operation object
     * @param definitions a map of Swagger models
     * @param swagger     a Swagger object representing the spec
     * @return Codegen Operation object
     */
    public CodegenOperation fromOperation(String path,
                                          String httpMethod,
                                          Operation operation,
                                          Map definitions,
                                          Swagger swagger) {
        CodegenOperation op = CodegenModelFactory.newInstance(CodegenModelType.OPERATION);
        Set imports = new HashSet();
        op.vendorExtensions = operation.getVendorExtensions();

        String operationId = getOrGenerateOperationId(operation, path, httpMethod);
        // remove prefix in operationId
        if (removeOperationIdPrefix) {
            int offset = operationId.indexOf('_');
            if (offset > -1) {
                operationId = operationId.substring(offset + 1);
            }
        }
        operationId = removeNonNameElementToCamelCase(operationId);
        op.path = path;
        op.operationId = toOperationId(operationId);
        op.summary = escapeText(operation.getSummary());
        op.unescapedNotes = operation.getDescription();
        op.notes = escapeText(operation.getDescription());
        op.hasConsumes = false;
        op.hasProduces = false;
        if (operation.isDeprecated() != null) {
            op.isDeprecated = operation.isDeprecated();
        }

        List consumes = new ArrayList();
        if (operation.getConsumes() != null) {
            if (operation.getConsumes().size() > 0) {
                // use consumes defined in the operation
                consumes = operation.getConsumes();
            } else {
                // empty list, do nothing to override global setting
            }
        } else if (swagger != null && swagger.getConsumes() != null && swagger.getConsumes().size() > 0) {
            // use consumes defined globally
            consumes = swagger.getConsumes();
            LOGGER.debug("No consumes defined in operation. Using global consumes (" + swagger.getConsumes() + ") for " + op.operationId);
        }

        // if "consumes" is defined (per operation or using global definition)
        if (consumes != null && consumes.size() > 0) {
            List> c = new ArrayList();
            int count = 0;
            for (String key : consumes) {
                Map mediaType = new HashMap();
                // escape quotation to avoid code injection
                if ("*/*".equals(key)) { // "*/*" is a special case, do nothing
                    mediaType.put("mediaType", key);
                } else {
                    mediaType.put("mediaType", escapeText(escapeQuotationMark(key)));
                }
                count += 1;
                if (count < consumes.size()) {
                    mediaType.put("hasMore", "true");
                } else {
                    mediaType.put("hasMore", null);
                }
                c.add(mediaType);
            }
            op.consumes = c;
            op.hasConsumes = true;
        }

        List produces = new ArrayList();
        if (operation.getProduces() != null) {
            if (operation.getProduces().size() > 0) {
                // use produces defined in the operation
                produces = operation.getProduces();
            } else {
                // empty list, do nothing to override global setting
            }
        } else if (swagger != null && swagger.getProduces() != null && swagger.getProduces().size() > 0) {
            // use produces defined globally
            produces = swagger.getProduces();
            LOGGER.debug("No produces defined in operation. Using global produces (" + swagger.getProduces() + ") for " + op.operationId);
        }

        // if "produces" is defined (per operation or using global definition)
        if (produces != null && !produces.isEmpty()) {
            List> c = new ArrayList>();
            int count = 0;
            for (String key : produces) {
                Map mediaType = new HashMap();
                // escape quotation to avoid code injection
                if ("*/*".equals(key)) { // "*/*" is a special case, do nothing
                    mediaType.put("mediaType", key);
                } else {
                    mediaType.put("mediaType", escapeText(escapeQuotationMark(key)));
                }
                count += 1;
                if (count < produces.size()) {
                    mediaType.put("hasMore", "true");
                } else {
                    mediaType.put("hasMore", null);
                }
                c.add(mediaType);
            }
            op.produces = c;
            op.hasProduces = true;
        }

        if (operation.getResponses() != null && !operation.getResponses().isEmpty()) {
            Response methodResponse = findMethodResponse(operation.getResponses());

            for (Map.Entry entry : operation.getResponses().entrySet()) {
                Response response = entry.getValue();
                CodegenResponse r = fromResponse(entry.getKey(), response);
                r.hasMore = true;
                if (r.baseType != null &&
                        !defaultIncludes.contains(r.baseType) &&
                        !languageSpecificPrimitives.contains(r.baseType)) {
                    imports.add(r.baseType);
                }
                r.isDefault = response == methodResponse;
                op.responses.add(r);
                if (Boolean.TRUE.equals(r.isBinary) && Boolean.TRUE.equals(r.isDefault)) {
                    op.isResponseBinary = Boolean.TRUE;
                }
                if (Boolean.TRUE.equals(r.isFile) && Boolean.TRUE.equals(r.isDefault)) {
                    op.isResponseFile = Boolean.TRUE;
                }
            }
            op.responses.get(op.responses.size() - 1).hasMore = false;

            if (methodResponse != null) {
                if (methodResponse.getSchema() != null) {
                    CodegenProperty cm = fromProperty("response", methodResponse.getSchema());

                    Property responseProperty = methodResponse.getSchema();

                    if (responseProperty instanceof ArrayProperty) {
                        ArrayProperty ap = (ArrayProperty) responseProperty;
                        CodegenProperty innerProperty = fromProperty("response", ap.getItems());
                        op.returnBaseType = innerProperty.baseType;
                    } else if (responseProperty instanceof MapProperty) {
                        MapProperty ap = (MapProperty) responseProperty;
                        CodegenProperty innerProperty = fromProperty("response", ap.getAdditionalProperties());
                        op.returnBaseType = innerProperty.baseType;
                    } else {
                        if (cm.complexType != null) {
                            op.returnBaseType = cm.complexType;
                        } else {
                            op.returnBaseType = cm.baseType;
                        }
                    }
                    op.defaultResponse = toDefaultValue(responseProperty);
                    op.returnType = cm.datatype;
                    op.hasReference = definitions != null && definitions.containsKey(op.returnBaseType);

                    // lookup discriminator
                    if (definitions != null) {
                        Model m = definitions.get(op.returnBaseType);
                        if (m != null) {
                            CodegenModel cmod = fromModel(op.returnBaseType, m, definitions);
                            op.discriminator = cmod.discriminator;
                        }
                    }

                    if (cm.isContainer) {
                        op.returnContainer = cm.containerType;
                        if ("map".equals(cm.containerType)) {
                            op.isMapContainer = true;
                        } else if ("list".equalsIgnoreCase(cm.containerType)) {
                            op.isListContainer = true;
                        } else if ("array".equalsIgnoreCase(cm.containerType)) {
                            op.isListContainer = true;
                        }
                    } else {
                        op.returnSimpleType = true;
                    }
                    if (languageSpecificPrimitives().contains(op.returnBaseType) || op.returnBaseType == null) {
                        op.returnTypeIsPrimitive = true;
                    }
                }
                addHeaders(methodResponse, op.responseHeaders);
            }
        }

        List parameters = operation.getParameters();
        CodegenParameter bodyParam = null;
        List allParams = new ArrayList();
        List bodyParams = new ArrayList();
        List pathParams = new ArrayList();
        List queryParams = new ArrayList();
        List headerParams = new ArrayList();
        List cookieParams = new ArrayList();
        List formParams = new ArrayList();

        if (parameters != null) {
            for (Parameter param : parameters) {
                CodegenParameter p = fromParameter(param, imports);
                // rename parameters to make sure all of them have unique names
                if (ensureUniqueParams) {
                    while (true) {
                        boolean exists = false;
                        for (CodegenParameter cp : allParams) {
                            if (p.paramName.equals(cp.paramName)) {
                                exists = true;
                                break;
                            }
                        }
                        if (exists) {
                            p.paramName = generateNextName(p.paramName);
                        } else {
                            break;
                        }
                    }
                }

                allParams.add(p);

                if (param instanceof QueryParameter) {
                    queryParams.add(p.copy());
                } else if (param instanceof PathParameter) {
                    pathParams.add(p.copy());
                } else if (param instanceof HeaderParameter) {
                    headerParams.add(p.copy());
                } else if (param instanceof CookieParameter) {
                    cookieParams.add(p.copy());
                } else if (param instanceof BodyParameter) {
                    bodyParam = p;
                    bodyParams.add(p.copy());
                } else if (param instanceof FormParameter) {
                    formParams.add(p.copy());
                }
                if (!p.required) {
                    op.hasOptionalParams = true;
                }
            }
        }

        for (String i : imports) {
            if (needToImport(i)) {
                op.imports.add(i);
            }
        }

        op.bodyParam = bodyParam;
        op.httpMethod = httpMethod.toUpperCase();

        // move "required" parameters in front of "optional" parameters
        if (sortParamsByRequiredFlag) {
            Collections.sort(allParams, (one, another) -> {
                if (one.required == another.required) return 0;
                else if (one.required) return -1;
                else return 1;
            });
        }

        op.allParams = addHasMore(allParams);
        op.bodyParams = addHasMore(bodyParams);
        op.pathParams = addHasMore(pathParams);
        op.queryParams = addHasMore(queryParams);
        op.headerParams = addHasMore(headerParams);
        // op.cookieParams = cookieParams;
        op.formParams = addHasMore(formParams);
        op.externalDocs = operation.getExternalDocs();
        // legacy support
        op.nickname = op.operationId;

        if (op.allParams.size() > 0) {
            op.hasParams = true;
        }

        // set Restful Flag
        op.isRestfulShow = op.isRestfulShow();
        op.isRestfulIndex = op.isRestfulIndex();
        op.isRestfulCreate = op.isRestfulCreate();
        op.isRestfulUpdate = op.isRestfulUpdate();
        op.isRestfulDestroy = op.isRestfulDestroy();
        op.isRestful = op.isRestful();

        return op;
    }

    private void addHeaders(Response response, List target) {
        if (response.getHeaders() != null) {
            for (Map.Entry headers : response.getHeaders().entrySet()) {
                target.add(fromProperty(headers.getKey(), headers.getValue()));
            }
        }
    }

    private static List addHasMore(List objs) {
        if (objs != null) {
            for (int i = 0; i < objs.size(); i++) {
                if (i > 0) {
                    objs.get(i).secondaryParam = true;
                }
                if (i < objs.size() - 1) {
                    objs.get(i).hasMore = true;
                }
            }
        }
        return objs;
    }

    private static Map addHasMore(Map objs) {
        if (objs != null) {
            for (int i = 0; i < objs.size() - 1; i++) {
                if (i > 0) {
                    objs.put("secondaryParam", true);
                }
                if (i < objs.size() - 1) {
                    objs.put("hasMore", true);
                }
            }
        }
        return objs;
    }

    /**
     * Generate the next name for the given name, i.e. append "2" to the base name if not ending with a number,
     * otherwise increase the number by 1. For example:
     * status    => status2
     * status2   => status3
     * myName100 => myName101
     *
     * @param name The base name
     * @return The next name for the base name
     */
    private static String generateNextName(String name) {
        Pattern pattern = Pattern.compile("\\d+\\z");
        Matcher matcher = pattern.matcher(name);
        if (matcher.find()) {
            String numStr = matcher.group();
            int num = Integer.parseInt(numStr) + 1;
            return name.substring(0, name.length() - numStr.length()) + num;
        } else {
            return name + "2";
        }
    }

    public String outputFolder() {
        return outputFolder.toLowerCase();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy