io.swagger.codegen.v3.generators.dotnet.CSharpClientCodegen Maven / Gradle / Ivy
package io.swagger.codegen.v3.generators.dotnet;
import com.google.common.collect.ImmutableMap;
import com.samskivert.mustache.Mustache;
import io.swagger.codegen.v3.CliOption;
import io.swagger.codegen.v3.CodegenConstants;
import io.swagger.codegen.v3.CodegenModel;
import io.swagger.codegen.v3.CodegenOperation;
import io.swagger.codegen.v3.CodegenParameter;
import io.swagger.codegen.v3.CodegenProperty;
import io.swagger.codegen.v3.CodegenType;
import io.swagger.codegen.v3.SupportingFile;
import io.swagger.v3.oas.models.media.Schema;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import static io.swagger.codegen.v3.CodegenConstants.HAS_ENUMS_EXT_NAME;
import static io.swagger.codegen.v3.CodegenConstants.IS_ENUM_EXT_NAME;
import static io.swagger.codegen.v3.generators.handlebars.ExtensionHelper.getBooleanValue;
import static org.apache.commons.lang3.StringUtils.isEmpty;
public class CSharpClientCodegen extends AbstractCSharpCodegen {
@SuppressWarnings({"hiding"})
private static final Logger LOGGER = LoggerFactory.getLogger(CSharpClientCodegen.class);
private static final String NET45 = "v4.5";
private static final String NET40 = "v4.0";
private static final String NET35 = "v3.5";
// TODO: v5.0 is PCL, not netstandard version 1.3, and not a specific .NET Framework. This needs to be updated,
// especially because it will conflict with .NET Framework 5.0 when released, and PCL 5 refers to Framework 4.0.
// We should support either NETSTANDARD, PCL, or Both… but the concepts shouldn't be mixed.
private static final String NETSTANDARD = "v5.0";
private static final String UWP = "uwp";
// Defines the sdk option for targeted frameworks, which differs from targetFramework and targetFrameworkNuget
private static final String MCS_NET_VERSION_KEY = "x-mcs-sdk";
protected String packageGuid = "{" + java.util.UUID.randomUUID().toString().toUpperCase() + "}";
protected String clientPackage = "IO.Swagger.Client";
protected String localVariablePrefix = "";
protected String apiDocPath = "docs/";
protected String modelDocPath = "docs/";
// Defines TargetFrameworkVersion in csproj files
protected String targetFramework = NET45;
// Defines nuget identifiers for target framework
protected String targetFrameworkNuget = "net45";
protected boolean supportsAsync = Boolean.TRUE;
protected boolean supportsUWP = Boolean.FALSE;
protected boolean netStandard = Boolean.FALSE;
protected boolean generatePropertyChanged = Boolean.FALSE;
protected boolean validatable = Boolean.TRUE;
protected Map regexModifiers;
protected final Map frameworks;
// By default, generated code is considered public
protected boolean nonPublicApi = Boolean.FALSE;
public CSharpClientCodegen() {
super();
supportsInheritance = true;
modelTemplateFiles.put("model.mustache", ".cs");
apiTemplateFiles.put("api.mustache", ".cs");
modelDocTemplateFiles.put("model_doc.mustache", ".md");
apiDocTemplateFiles.put("api_doc.mustache", ".md");
hideGenerationTimestamp = Boolean.TRUE;
cliOptions.clear();
// CLI options
addOption(CodegenConstants.PACKAGE_NAME,
"C# package name (convention: Title.Case).",
this.packageName);
addOption(CodegenConstants.PACKAGE_VERSION,
"C# package version.",
this.packageVersion);
addOption(CodegenConstants.SOURCE_FOLDER,
CodegenConstants.SOURCE_FOLDER_DESC,
sourceFolder);
addOption(CodegenConstants.OPTIONAL_PROJECT_GUID,
CodegenConstants.OPTIONAL_PROJECT_GUID_DESC,
null);
addOption(CodegenConstants.INTERFACE_PREFIX,
CodegenConstants.INTERFACE_PREFIX_DESC,
interfacePrefix);
CliOption framework = new CliOption(
CodegenConstants.DOTNET_FRAMEWORK,
CodegenConstants.DOTNET_FRAMEWORK_DESC
);
frameworks = new ImmutableMap.Builder()
.put(NET35, ".NET Framework 3.5 compatible")
.put(NET40, ".NET Framework 4.0 compatible")
.put(NET45, ".NET Framework 4.5+ compatible")
.put(NETSTANDARD, ".NET Standard 1.3 compatible")
.put(UWP, "Universal Windows Platform (IMPORTANT: this will be decommissioned and replaced by v5.0)")
.build();
framework.defaultValue(this.targetFramework);
framework.setEnum(frameworks);
cliOptions.add(framework);
CliOption modelPropertyNaming = new CliOption(CodegenConstants.MODEL_PROPERTY_NAMING, CodegenConstants.MODEL_PROPERTY_NAMING_DESC);
cliOptions.add(modelPropertyNaming.defaultValue("PascalCase"));
// CLI Switches
addSwitch(CodegenConstants.HIDE_GENERATION_TIMESTAMP,
CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC,
this.hideGenerationTimestamp);
addSwitch(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG,
CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG_DESC,
this.sortParamsByRequiredFlag);
addSwitch(CodegenConstants.USE_DATETIME_OFFSET,
CodegenConstants.USE_DATETIME_OFFSET_DESC,
this.useDateTimeOffsetFlag);
addSwitch(CodegenConstants.USE_COLLECTION,
CodegenConstants.USE_COLLECTION_DESC,
this.useCollection);
addSwitch(CodegenConstants.RETURN_ICOLLECTION,
CodegenConstants.RETURN_ICOLLECTION_DESC,
this.returnICollection);
addSwitch(CodegenConstants.OPTIONAL_METHOD_ARGUMENT,
"C# Optional method argument, e.g. void square(int x=10) (.net 4.0+ only).",
this.optionalMethodArgumentFlag);
addSwitch(CodegenConstants.OPTIONAL_ASSEMBLY_INFO,
CodegenConstants.OPTIONAL_ASSEMBLY_INFO_DESC,
this.optionalAssemblyInfoFlag);
addSwitch(CodegenConstants.OPTIONAL_PROJECT_FILE,
CodegenConstants.OPTIONAL_PROJECT_FILE_DESC,
this.optionalProjectFileFlag);
addSwitch(CodegenConstants.OPTIONAL_EMIT_DEFAULT_VALUES,
CodegenConstants.OPTIONAL_EMIT_DEFAULT_VALUES_DESC,
this.optionalEmitDefaultValue);
addSwitch(CodegenConstants.GENERATE_PROPERTY_CHANGED,
CodegenConstants.PACKAGE_DESCRIPTION_DESC,
this.generatePropertyChanged);
// NOTE: This will reduce visibility of all public members in templates. Users can use InternalsVisibleTo
// https://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.internalsvisibletoattribute(v=vs.110).aspx
// to expose to shared code if the generated code is not embedded into another project. Otherwise, users of codegen
// should rely on default public visibility.
addSwitch(CodegenConstants.NON_PUBLIC_API,
CodegenConstants.NON_PUBLIC_API_DESC,
this.nonPublicApi);
addSwitch(CodegenConstants.ALLOW_UNICODE_IDENTIFIERS,
CodegenConstants.ALLOW_UNICODE_IDENTIFIERS_DESC,
this.allowUnicodeIdentifiers);
addSwitch(CodegenConstants.NETCORE_PROJECT_FILE,
CodegenConstants.NETCORE_PROJECT_FILE_DESC,
this.netCoreProjectFileFlag);
addSwitch(CodegenConstants.VALIDATABLE,
CodegenConstants.VALIDATABLE_DESC,
this.validatable);
regexModifiers = new HashMap();
regexModifiers.put('i', "IgnoreCase");
regexModifiers.put('m', "Multiline");
regexModifiers.put('s', "Singleline");
regexModifiers.put('x', "IgnorePatternWhitespace");
}
@Override
public void processOpts() {
super.processOpts();
/*
* NOTE: When supporting boolean additionalProperties, you should read the value and write it back as a boolean.
* This avoids oddities where additionalProperties contains "false" rather than false, which will cause the
* templating engine to behave unexpectedly.
*
* Use the pattern:
* if (additionalProperties.containsKey(prop)) convertPropertyToBooleanAndWriteBack(prop);
*/
if (additionalProperties.containsKey(CodegenConstants.MODEL_PROPERTY_NAMING)) {
setModelPropertyNaming((String) additionalProperties.get(CodegenConstants.MODEL_PROPERTY_NAMING));
}
if (isEmpty(apiPackage)) {
setApiPackage("Api");
}
if (isEmpty(modelPackage)) {
setModelPackage("Model");
}
clientPackage = "Client";
Boolean excludeTests = false;
if (additionalProperties.containsKey(CodegenConstants.EXCLUDE_TESTS)) {
excludeTests = convertPropertyToBooleanAndWriteBack(CodegenConstants.EXCLUDE_TESTS);
}
if (additionalProperties.containsKey(CodegenConstants.VALIDATABLE)) {
setValidatable(convertPropertyToBooleanAndWriteBack(CodegenConstants.VALIDATABLE));
} else {
additionalProperties.put(CodegenConstants.VALIDATABLE, validatable);
}
if (additionalProperties.containsKey(CodegenConstants.DOTNET_FRAMEWORK)) {
setTargetFramework((String) additionalProperties.get(CodegenConstants.DOTNET_FRAMEWORK));
} else {
// Ensure default is set.
setTargetFramework(NET45);
additionalProperties.put(CodegenConstants.DOTNET_FRAMEWORK, this.targetFramework);
}
if (NET35.equals(this.targetFramework)) {
// This is correct, mono will require you build .NET 3.5 sources using 4.0 SDK
additionalProperties.put(MCS_NET_VERSION_KEY, "4");
additionalProperties.put("net35", true);
if (additionalProperties.containsKey(CodegenConstants.SUPPORTS_ASYNC)) {
LOGGER.warn(".NET 3.5 generator does not support async.");
additionalProperties.remove(CodegenConstants.SUPPORTS_ASYNC);
}
setTargetFrameworkNuget("net35");
setValidatable(Boolean.FALSE);
setSupportsAsync(Boolean.FALSE);
} else if (NETSTANDARD.equals(this.targetFramework)) {
// TODO: NETSTANDARD here is misrepresenting a PCL v5.0 which supports .NET Framework 4.6+, .NET Core 1.0, and Windows Universal 10.0
additionalProperties.put(MCS_NET_VERSION_KEY, "4.6-api");
if (additionalProperties.containsKey("supportsUWP")) {
LOGGER.warn(".NET " + NETSTANDARD + " generator does not support UWP.");
additionalProperties.remove("supportsUWP");
}
// TODO: NETSTANDARD=v5.0 and targetFrameworkNuget=netstandard1.3. These need to sync.
setTargetFrameworkNuget("netstandard1.3");
setSupportsAsync(Boolean.TRUE);
setSupportsUWP(Boolean.FALSE);
setNetStandard(Boolean.TRUE);
//Tests not yet implemented for .NET Standard codegen
//Todo implement it
excludeTests = true;
} else if (UWP.equals(this.targetFramework)) {
setTargetFrameworkNuget("uwp");
setSupportsAsync(Boolean.TRUE);
setSupportsUWP(Boolean.TRUE);
} else if (NET40.equals(this.targetFramework)) {
additionalProperties.put(MCS_NET_VERSION_KEY, "4");
additionalProperties.put("isNet40", true);
if (additionalProperties.containsKey(CodegenConstants.SUPPORTS_ASYNC)) {
LOGGER.warn(".NET " + NET40 + " generator does not support async.");
additionalProperties.remove(CodegenConstants.SUPPORTS_ASYNC);
}
setTargetFrameworkNuget("net40");
setSupportsAsync(Boolean.FALSE);
} else {
additionalProperties.put(MCS_NET_VERSION_KEY, "4.5.2-api");
setTargetFrameworkNuget("net45");
setSupportsAsync(Boolean.TRUE);
}
if (additionalProperties.containsKey(CodegenConstants.GENERATE_PROPERTY_CHANGED)) {
if (NET35.equals(targetFramework)) {
LOGGER.warn(CodegenConstants.GENERATE_PROPERTY_CHANGED + " is only supported by generated code for .NET 4+.");
additionalProperties.remove(CodegenConstants.GENERATE_PROPERTY_CHANGED);
} else if (NETSTANDARD.equals(targetFramework)) {
LOGGER.warn(CodegenConstants.GENERATE_PROPERTY_CHANGED + " is not supported in .NET Standard generated code.");
additionalProperties.remove(CodegenConstants.GENERATE_PROPERTY_CHANGED);
} else if (Boolean.TRUE.equals(netCoreProjectFileFlag)) {
LOGGER.warn(CodegenConstants.GENERATE_PROPERTY_CHANGED + " is not supported in .NET Core csproj project format.");
additionalProperties.remove(CodegenConstants.GENERATE_PROPERTY_CHANGED);
} else {
setGeneratePropertyChanged(convertPropertyToBooleanAndWriteBack(CodegenConstants.GENERATE_PROPERTY_CHANGED));
}
}
additionalProperties.put(CodegenConstants.API_PACKAGE, apiPackage);
additionalProperties.put(CodegenConstants.MODEL_PACKAGE, modelPackage);
additionalProperties.put("clientPackage", clientPackage);
additionalProperties.put(CodegenConstants.EXCLUDE_TESTS, excludeTests);
additionalProperties.put(CodegenConstants.VALIDATABLE, this.validatable);
additionalProperties.put(CodegenConstants.SUPPORTS_ASYNC, this.supportsAsync);
additionalProperties.put("supportsUWP", this.supportsUWP);
additionalProperties.put("netStandard", this.netStandard);
additionalProperties.put("targetFrameworkNuget", this.targetFrameworkNuget);
// TODO: either remove this and update templates to match the "optionalEmitDefaultValues" property, or rename that property.
additionalProperties.put("emitDefaultValue", optionalEmitDefaultValue);
if (additionalProperties.containsKey(CodegenConstants.OPTIONAL_PROJECT_FILE)) {
setOptionalProjectFileFlag(convertPropertyToBooleanAndWriteBack(CodegenConstants.OPTIONAL_PROJECT_FILE));
} else {
additionalProperties.put(CodegenConstants.OPTIONAL_PROJECT_FILE, optionalProjectFileFlag);
}
if (additionalProperties.containsKey(CodegenConstants.OPTIONAL_PROJECT_GUID)) {
setPackageGuid((String) additionalProperties.get(CodegenConstants.OPTIONAL_PROJECT_GUID));
} else {
additionalProperties.put(CodegenConstants.OPTIONAL_PROJECT_GUID, packageGuid);
}
if (additionalProperties.containsKey(CodegenConstants.OPTIONAL_METHOD_ARGUMENT)) {
setOptionalMethodArgumentFlag(convertPropertyToBooleanAndWriteBack(CodegenConstants.OPTIONAL_METHOD_ARGUMENT));
} else {
additionalProperties.put(CodegenConstants.OPTIONAL_METHOD_ARGUMENT, optionalMethodArgumentFlag);
}
if (additionalProperties.containsKey(CodegenConstants.OPTIONAL_ASSEMBLY_INFO)) {
setOptionalAssemblyInfoFlag(convertPropertyToBooleanAndWriteBack(CodegenConstants.OPTIONAL_ASSEMBLY_INFO));
} else {
additionalProperties.put(CodegenConstants.OPTIONAL_ASSEMBLY_INFO, optionalAssemblyInfoFlag);
}
if (additionalProperties.containsKey(CodegenConstants.NON_PUBLIC_API)) {
setNonPublicApi(convertPropertyToBooleanAndWriteBack(CodegenConstants.NON_PUBLIC_API));
} else {
additionalProperties.put(CodegenConstants.NON_PUBLIC_API, isNonPublicApi());
}
final String testPackageName = testPackageName();
String packageFolder = sourceFolder + File.separator + packageName;
String clientPackageDir = packageFolder + File.separator + clientPackage;
String testPackageFolder = testFolder + File.separator + testPackageName;
additionalProperties.put("testPackageName", testPackageName);
//Compute the relative path to the bin directory where the external assemblies live
//This is necessary to properly generate the project file
int packageDepth = packageFolder.length() - packageFolder.replace(java.io.File.separator, "").length();
String binRelativePath = "..\\";
for (int i = 0; i < packageDepth; i = i + 1)
binRelativePath += "..\\";
binRelativePath += "vendor";
additionalProperties.put("binRelativePath", binRelativePath);
supportingFiles.add(new SupportingFile("IApiAccessor.mustache",
clientPackageDir, "IApiAccessor.cs"));
supportingFiles.add(new SupportingFile("Configuration.mustache",
clientPackageDir, "Configuration.cs"));
supportingFiles.add(new SupportingFile("ApiClient.mustache",
clientPackageDir, "ApiClient.cs"));
supportingFiles.add(new SupportingFile("ApiException.mustache",
clientPackageDir, "ApiException.cs"));
supportingFiles.add(new SupportingFile("ApiResponse.mustache",
clientPackageDir, "ApiResponse.cs"));
supportingFiles.add(new SupportingFile("ExceptionFactory.mustache",
clientPackageDir, "ExceptionFactory.cs"));
supportingFiles.add(new SupportingFile("SwaggerDateConverter.mustache",
clientPackageDir, "SwaggerDateConverter.cs"));
if (NET40.equals(this.targetFramework)) {
// .net 4.0 doesn't include ReadOnlyDictionary…
supportingFiles.add(new SupportingFile("ReadOnlyDictionary.mustache",
clientPackageDir, "ReadOnlyDictionary.cs"));
}
if (Boolean.FALSE.equals(this.netStandard) && Boolean.FALSE.equals(this.netCoreProjectFileFlag)) {
supportingFiles.add(new SupportingFile("compile.mustache", "", "build.bat"));
supportingFiles.add(new SupportingFile("compile-mono.sh.mustache", "", "build.sh"));
// copy package.config to nuget's standard location for project-level installs
supportingFiles.add(new SupportingFile("packages.config.mustache", packageFolder + File.separator, "packages.config"));
// .travis.yml for travis-ci.org CI
supportingFiles.add(new SupportingFile("travis.mustache", "", ".travis.yml"));
} else if (Boolean.FALSE.equals(this.netCoreProjectFileFlag)) {
supportingFiles.add(new SupportingFile("project.json.mustache", packageFolder + File.separator, "project.json"));
}
supportingFiles.add(new SupportingFile("IReadableConfiguration.mustache",
clientPackageDir, "IReadableConfiguration.cs"));
supportingFiles.add(new SupportingFile("GlobalConfiguration.mustache",
clientPackageDir, "GlobalConfiguration.cs"));
// Only write out test related files if excludeTests is unset or explicitly set to false (see start of this method)
if (Boolean.FALSE.equals(excludeTests)) {
// shell script to run the nunit test
supportingFiles.add(new SupportingFile("mono_nunit_test.mustache", "", "mono_nunit_test.sh"));
modelTestTemplateFiles.put("model_test.mustache", ".cs");
apiTestTemplateFiles.put("api_test.mustache", ".cs");
if (Boolean.FALSE.equals(this.netCoreProjectFileFlag)) {
supportingFiles.add(new SupportingFile("packages_test.config.mustache", testPackageFolder + File.separator, "packages.config"));
}
if (NET40.equals(this.targetFramework)) {
// Include minimal tests for modifications made to JsonSubTypes, since code is quite different for .net 4.0 from original implementation
supportingFiles.add(new SupportingFile("JsonSubTypesTests.mustache",
testPackageFolder + File.separator + "Client",
"JsonSubTypesTests.cs"));
}
}
if (Boolean.TRUE.equals(generatePropertyChanged)) {
supportingFiles.add(new SupportingFile("FodyWeavers.xml", packageFolder, "FodyWeavers.xml"));
}
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh"));
supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore"));
if (optionalAssemblyInfoFlag && Boolean.FALSE.equals(this.netCoreProjectFileFlag)) {
supportingFiles.add(new SupportingFile("AssemblyInfo.mustache", packageFolder + File.separator + "Properties", "AssemblyInfo.cs"));
}
if (optionalProjectFileFlag) {
supportingFiles.add(new SupportingFile("Solution.mustache", "", packageName + ".sln"));
if (Boolean.TRUE.equals(this.netCoreProjectFileFlag)) {
supportingFiles.add(new SupportingFile("netcore_project.mustache", packageFolder, packageName + ".csproj"));
} else {
supportingFiles.add(new SupportingFile("Project.mustache", packageFolder, packageName + ".csproj"));
if (Boolean.FALSE.equals(this.netStandard)) {
supportingFiles.add(new SupportingFile("nuspec.mustache", packageFolder, packageName + ".nuspec"));
}
}
if (Boolean.FALSE.equals(excludeTests)) {
// NOTE: This exists here rather than previous excludeTests block because the test project is considered an optional project file.
if (Boolean.TRUE.equals(this.netCoreProjectFileFlag)) {
supportingFiles.add(new SupportingFile("netcore_testproject.mustache", testPackageFolder, testPackageName + ".csproj"));
} else {
supportingFiles.add(new SupportingFile("TestProject.mustache", testPackageFolder, testPackageName + ".csproj"));
}
}
}
additionalProperties.put("apiDocPath", apiDocPath);
additionalProperties.put("modelDocPath", modelDocPath);
}
public void setModelPropertyNaming(String naming) {
if ("original".equals(naming) || "camelCase".equals(naming) ||
"PascalCase".equals(naming) || "snake_case".equals(naming)) {
this.modelPropertyNaming = naming;
} else {
throw new IllegalArgumentException("Invalid model property naming '" +
naming + "'. Must be 'original', 'camelCase', " +
"'PascalCase' or 'snake_case'");
}
}
public String getModelPropertyNaming() {
return this.modelPropertyNaming;
}
@Override
public Map postProcessOperations(Map objs) {
super.postProcessOperations(objs);
if (objs != null) {
Map operations = (Map) objs.get("operations");
if (operations != null) {
List ops = (List) operations.get("operation");
for (CodegenOperation operation : ops) {
if (operation.returnType != null) {
operation.returnContainer = operation.returnType;
if (this.returnICollection && (
operation.returnType.startsWith("List") ||
operation.returnType.startsWith("Collection"))) {
// NOTE: ICollection works for both List and Collection
int genericStart = operation.returnType.indexOf("<");
if (genericStart > 0) {
operation.returnType = "ICollection" + operation.returnType.substring(genericStart);
}
}
}
}
}
}
return objs;
}
@Override
public CodegenType getTag() {
return CodegenType.CLIENT;
}
@Override
public String getName() {
return "csharp";
}
@Override
public String getHelp() {
return "Generates a CSharp client library.";
}
public void setOptionalAssemblyInfoFlag(boolean flag) {
this.optionalAssemblyInfoFlag = flag;
}
@Override
public CodegenModel fromModel(String name, Schema schema, Map allDefinitions) {
CodegenModel codegenModel = super.fromModel(name, schema, allDefinitions);
if (allDefinitions != null && codegenModel != null && codegenModel.parent != null) {
final Schema parentModel = allDefinitions.get(toModelName(codegenModel.parent));
if (parentModel != null) {
final CodegenModel parentCodegenModel = super.fromModel(codegenModel.parent, parentModel, allDefinitions);
boolean hasEnums = getBooleanValue(codegenModel, HAS_ENUMS_EXT_NAME);
if (hasEnums) {
codegenModel = this.reconcileInlineEnums(codegenModel, parentCodegenModel);
}
Map propertyHash = new HashMap<>(codegenModel.vars.size());
for (final CodegenProperty property : codegenModel.vars) {
propertyHash.put(property.name, property);
}
for (final CodegenProperty property : codegenModel.readWriteVars) {
if (property.defaultValue == null && property.baseName.equals(parentCodegenModel.discriminator)) {
property.defaultValue = "\"" + name + "\"";
}
}
CodegenProperty last = null;
for (final CodegenProperty property : parentCodegenModel.vars) {
// helper list of parentVars simplifies templating
if (!propertyHash.containsKey(property.name)) {
final CodegenProperty parentVar = property.clone();
parentVar.getVendorExtensions().put(CodegenConstants.IS_INHERITED_EXT_NAME, Boolean.TRUE);
parentVar.getVendorExtensions().put(CodegenConstants.HAS_MORE_EXT_NAME, Boolean.TRUE);
last = parentVar;
LOGGER.info("adding parent variable {}", property.name);
codegenModel.parentVars.add(parentVar);
}
}
if (last != null) {
last.getVendorExtensions().put(CodegenConstants.HAS_MORE_EXT_NAME, Boolean.FALSE);
}
}
}
// Cleanup possible duplicates. Currently, readWriteVars can contain the same property twice. May or may not be isolated to C#.
if (codegenModel != null && codegenModel.readWriteVars != null && codegenModel.readWriteVars.size() > 1) {
int length = codegenModel.readWriteVars.size() - 1;
for (int i = length; i > (length / 2); i--) {
final CodegenProperty codegenProperty = codegenModel.readWriteVars.get(i);
// If the property at current index is found earlier in the list, remove this last instance.
if (codegenModel.readWriteVars.indexOf(codegenProperty) < i) {
codegenModel.readWriteVars.remove(i);
}
}
}
return codegenModel;
}
public void setOptionalProjectFileFlag(boolean flag) {
this.optionalProjectFileFlag = flag;
}
public void setPackageGuid(String packageGuid) {
this.packageGuid = packageGuid;
}
@Override
public void postProcessParameter(CodegenParameter parameter) {
postProcessPattern(parameter.pattern, parameter.vendorExtensions);
super.postProcessParameter(parameter);
}
@Override
public void postProcessModelProperty(CodegenModel model, CodegenProperty property) {
postProcessPattern(property.pattern, property.vendorExtensions);
super.postProcessModelProperty(model, property);
}
@Override
protected void fixUpParentAndInterfaces(CodegenModel codegenModel, Map allModels) {
super.fixUpParentAndInterfaces(codegenModel, allModels);
final CodegenModel parentModel = codegenModel.getParentModel();
if (parentModel == null || (codegenModel.getReadWriteVars() == null || codegenModel.getReadWriteVars().isEmpty()) || (parentModel.getVars() == null || parentModel.getVars().isEmpty())) {
return;
}
codegenModel.setParentVars(parentModel.getVars());
parentModel.getVars().forEach(parentProperty -> {
codegenModel.getReadWriteVars().stream()
.filter(codegenProperty -> parentProperty.getName().equalsIgnoreCase(codegenProperty.getName()))
.findFirst()
.ifPresent(codegenProperty -> {
codegenProperty.setDatatype(parentProperty.getDatatype());
codegenProperty.setDatatypeWithEnum(parentProperty.getDatatypeWithEnum());
});
});
}
/*
* The swagger pattern spec follows the Perl convention and style of modifiers. .NET
* does not support this syntax directly so we need to convert the pattern to a .NET compatible
* format and apply modifiers in a compatible way.
* See https://msdn.microsoft.com/en-us/library/yd1hzczs(v=vs.110).aspx for .NET options.
* See https://github.com/swagger-api/swagger-codegen/pull/2794 for Python's initial implementation from which this is copied.
*/
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) {
pattern = String.format("/%s/", pattern);;
i = pattern.lastIndexOf('/');
}
String regex = pattern.substring(1, i).replace("'", "\'");
List modifiers = new ArrayList();
// perl requires an explicit modifier to be culture specific and .NET is the reverse.
modifiers.add("CultureInvariant");
for (char c : pattern.substring(i).toCharArray()) {
if (regexModifiers.containsKey(c)) {
String modifier = regexModifiers.get(c);
modifiers.add(modifier);
} else if (c == 'l') {
modifiers.remove("CultureInvariant");
}
}
vendorExtensions.put("x-regex", regex);
vendorExtensions.put("x-modifiers", modifiers);
}
}
public void setTargetFramework(String dotnetFramework) {
if (!frameworks.containsKey(dotnetFramework)) {
LOGGER.warn("Invalid .NET framework version, defaulting to " + this.targetFramework);
} else {
this.targetFramework = dotnetFramework;
}
LOGGER.info("Generating code for .NET Framework " + this.targetFramework);
}
private CodegenModel reconcileInlineEnums(CodegenModel codegenModel, CodegenModel parentCodegenModel) {
// This generator uses inline classes to define enums, which breaks when
// dealing with models that have subTypes. To clean this up, we will analyze
// the parent and child models, look for enums that match, and remove
// them from the child models and leave them in the parent.
// Because the child models extend the parents, the enums will be available via the parent.
// Only bother with reconciliation if the parent model has enums.
boolean hasEnums = getBooleanValue(parentCodegenModel, HAS_ENUMS_EXT_NAME);
if (hasEnums) {
// Get the properties for the parent and child models
final List parentModelCodegenProperties = parentCodegenModel.vars;
List codegenProperties = codegenModel.vars;
// Iterate over all of the parent model properties
boolean removedChildEnum = false;
for (CodegenProperty parentModelCodegenPropery : parentModelCodegenProperties) {
// Look for enums
boolean isEnum = getBooleanValue(parentModelCodegenPropery, IS_ENUM_EXT_NAME);
if (isEnum) {
// Now that we have found an enum in the parent class,
// and search the child class for the same enum.
Iterator iterator = codegenProperties.iterator();
while (iterator.hasNext()) {
CodegenProperty codegenProperty = iterator.next();
isEnum = getBooleanValue(codegenProperty, IS_ENUM_EXT_NAME);
if (isEnum && codegenProperty.equals(parentModelCodegenPropery)) {
// We found an enum in the child class that is
// a duplicate of the one in the parent, so remove it.
iterator.remove();
removedChildEnum = true;
}
}
}
}
if (removedChildEnum) {
// If we removed an entry from this model's vars, we need to ensure hasMore is updated
int count = 0, numVars = codegenProperties.size();
for (CodegenProperty codegenProperty : codegenProperties) {
count += 1;
codegenProperty.getVendorExtensions().put(CodegenConstants.HAS_MORE_EXT_NAME, count < numVars);
}
codegenModel.vars = codegenProperties;
}
}
return codegenModel;
}
@Override
public String toEnumVarName(String value, String datatype) {
if (value.length() == 0) {
return "Empty";
}
// for symbol, e.g. $, #
if (getSymbolName(value) != null) {
return camelize(getSymbolName(value));
}
// number
if(datatype.startsWith("int") || datatype.startsWith("long") ||
datatype.startsWith("double") || datatype.startsWith("float")) {
String varName = "NUMBER_" + value;
varName = varName.replaceAll("-", "MINUS_");
varName = varName.replaceAll("\\+", "PLUS_");
varName = varName.replaceAll("\\.", "_DOT_");
return varName;
}
// string
String var = value.replaceAll("_", " ");
//var = WordUtils.capitalizeFully(var);
var = camelize(var);
var = var.replaceAll("\\W+", "");
if (var.matches("\\d.*")) {
return "_" + var;
} else {
return var;
}
}
@Override
public String toVarName(String name) {
// sanitize name
name = sanitizeName(name);
// if it's all uppper case, do nothing
if (name.matches("^[A-Z_]*$")) {
return name;
}
name = getNameUsingModelPropertyNaming(name);
// for reserved word or word starting with number, append _
if (isReservedWord(name) || name.matches("^\\d.*")) {
name = escapeReservedWord(name);
}
return name;
}
public String getNameUsingModelPropertyNaming(String name) {
switch (CodegenConstants.MODEL_PROPERTY_NAMING_TYPE.valueOf(getModelPropertyNaming())) {
case original: return name;
case camelCase: return camelize(name, true);
case PascalCase: return camelize(name);
case snake_case: return underscore(name);
default: throw new IllegalArgumentException("Invalid model property naming '" +
name + "'. Must be 'original', 'camelCase', " +
"'PascalCase' or 'snake_case'");
}
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public void setPackageVersion(String packageVersion) {
this.packageVersion = packageVersion;
}
public void setTargetFrameworkNuget(String targetFrameworkNuget) {
this.targetFrameworkNuget = targetFrameworkNuget;
}
public void setSupportsAsync(Boolean supportsAsync) {
this.supportsAsync = supportsAsync;
}
public void setSupportsUWP(Boolean supportsUWP) {
this.supportsUWP = supportsUWP;
}
public void setNetStandard(Boolean netStandard) {
this.netStandard = netStandard;
}
public void setGeneratePropertyChanged(final Boolean generatePropertyChanged) {
this.generatePropertyChanged = generatePropertyChanged;
}
public boolean isNonPublicApi() {
return nonPublicApi;
}
public void setNonPublicApi(final boolean nonPublicApi) {
this.nonPublicApi = nonPublicApi;
}
public void setValidatable(boolean validatable) {
this.validatable = validatable;
}
@Override
public String toModelDocFilename(String name) {
return toModelFilename(name);
}
@Override
public String apiDocFileFolder() {
return (outputFolder + "/" + apiDocPath).replace('/', File.separatorChar);
}
@Override
public String modelDocFileFolder() {
return (outputFolder + "/" + modelDocPath).replace('/', File.separatorChar);
}
@Override
public String apiTestFileFolder() {
return outputFolder + File.separator + testFolder + File.separator + testPackageName() + File.separator + apiPackage();
}
@Override
public String modelTestFileFolder() {
return outputFolder + File.separator + testFolder + File.separator + testPackageName() + File.separator + modelPackage();
}
@Override
public Mustache.Compiler processCompiler(Mustache.Compiler compiler) {
// To avoid unexpected behaviors when options are passed programmatically such as { "supportsAsync": "" }
return super.processCompiler(compiler).emptyStringIsFalse(true);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy