mirror of
https://github.com/OpenAPITools/openapi-generator.git
synced 2026-04-27 02:54:24 +00:00
improve speed of generator (#23272)
* improve speed of generator * implement suggested fixes from CR. fix some general nitpicks. * implement suggested fixes from CR. fix some general nitpicks. * implement suggested fixes from CR. fix some general nitpicks. * revert some changes * fix * Revert "fix" This reverts commit607c678874. * Revert "revert some changes" This reverts commit3b8e4c2838. * fix
This commit is contained in:
+175
-169
@@ -79,6 +79,7 @@ import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
@@ -244,10 +245,18 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
// sort operations by default
|
||||
protected boolean skipSortingOperations = false;
|
||||
|
||||
protected final static Pattern XML_MIME_PATTERN = Pattern.compile("(?i)application\\/(.*)[+]?xml(;.*)?");
|
||||
protected final static Pattern JSON_MIME_PATTERN = Pattern.compile("(?i)application\\/json(;.*)?");
|
||||
protected final static Pattern JSON_VENDOR_MIME_PATTERN = Pattern.compile("(?i)application\\/vnd.(.*)+json(;.*)?");
|
||||
protected final static Pattern XML_MIME_PATTERN = Pattern.compile("(?i)application/(.*)[+]?xml(;.*)?");
|
||||
protected final static Pattern JSON_MIME_PATTERN = Pattern.compile("(?i)application/json(;.*)?");
|
||||
protected final static Pattern JSON_VENDOR_MIME_PATTERN = Pattern.compile("(?i)application/vnd.(.*)+json(;.*)?");
|
||||
private static final Pattern COMMON_PREFIX_ENUM_NAME = Pattern.compile("[a-zA-Z0-9]+\\z");
|
||||
/** Matches a trailing run of digits at the end of a name, used by {@link #generateNextName}. */
|
||||
private static final Pattern TRAILING_DIGITS = Pattern.compile("\\d+\\z");
|
||||
/**
|
||||
* Cache of removeCharRegEx string → compiled {@link Pattern} with {@link Pattern#UNICODE_CHARACTER_CLASS}.
|
||||
* {@code sanitizeName} is called once per unique (name, regex, exceptions) tuple, so the regex string
|
||||
* (almost always {@code "\\W"}) would otherwise be recompiled for every unique property/model name.
|
||||
*/
|
||||
private static final ConcurrentHashMap<String, Pattern> REMOVE_CHAR_UNICODE_PATTERN_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* True if the code generator supports multiple class inheritance.
|
||||
@@ -472,7 +481,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
private void registerMustacheLambdas() {
|
||||
ImmutableMap<String, Lambda> lambdas = addMustacheLambdas().build();
|
||||
|
||||
if (lambdas.size() == 0) {
|
||||
if (lambdas.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -552,7 +561,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
List<Map<String, String>> modelsImports = modelsAttrs.getImportsOrEmpty();
|
||||
for (ModelMap mo : modelsAttrs.getModels()) {
|
||||
CodegenModel cm = mo.getModel();
|
||||
if (cm.oneOf.size() > 0) {
|
||||
if (!cm.oneOf.isEmpty()) {
|
||||
cm.vendorExtensions.put(X_IS_ONE_OF_INTERFACE, true);
|
||||
for (String one : cm.oneOf) {
|
||||
if (!additionalDataMap.containsKey(one)) {
|
||||
@@ -594,11 +603,9 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
* @return
|
||||
*/
|
||||
private boolean codegenPropertyIsNew(CodegenModel model, CodegenProperty property) {
|
||||
return model.parentModel == null
|
||||
? false
|
||||
: model.parentModel.allVars.stream().anyMatch(p ->
|
||||
return model.parentModel != null && model.parentModel.allVars.stream().anyMatch(p ->
|
||||
p.name.equals(property.name) &&
|
||||
(p.dataType.equals(property.dataType) == false || p.datatypeWithEnum.equals(property.datatypeWithEnum) == false));
|
||||
(!p.dataType.equals(property.dataType) || !p.datatypeWithEnum.equals(property.datatypeWithEnum)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -742,7 +749,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
for (CodegenProperty cp : model.allVars) {
|
||||
// detect self import
|
||||
if (cp.dataType.equalsIgnoreCase(model.classname) ||
|
||||
(cp.isContainer && cp.items != null && cp.items.dataType.equalsIgnoreCase(model.classname))) {
|
||||
(cp.isContainer && cp.items != null && cp.items.dataType.equalsIgnoreCase(model.classname))) {
|
||||
model.imports.remove(model.classname); // remove self import
|
||||
cp.isSelfReference = true;
|
||||
}
|
||||
@@ -784,7 +791,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
}
|
||||
|
||||
private void setCircularReferencesOnProperties(final String root,
|
||||
final Map<String, List<CodegenProperty>> dependencyMap) {
|
||||
final Map<String, List<CodegenProperty>> dependencyMap) {
|
||||
dependencyMap.getOrDefault(root, new ArrayList<>())
|
||||
.forEach(prop -> {
|
||||
final List<String> unvisited =
|
||||
@@ -797,9 +804,9 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
}
|
||||
|
||||
private boolean isCircularReference(final String root,
|
||||
final Set<String> visited,
|
||||
final List<String> unvisited,
|
||||
final Map<String, List<CodegenProperty>> dependencyMap) {
|
||||
final Set<String> visited,
|
||||
final List<String> unvisited,
|
||||
final Map<String, List<CodegenProperty>> dependencyMap) {
|
||||
for (int i = 0; i < unvisited.size(); i++) {
|
||||
final String next = unvisited.get(i);
|
||||
if (!visited.contains(next)) {
|
||||
@@ -832,7 +839,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
CodegenModel cm = mo.getModel();
|
||||
|
||||
// for enum model
|
||||
if (Boolean.TRUE.equals(cm.isEnum) && cm.allowableValues != null) {
|
||||
if (cm.isEnum && cm.allowableValues != null) {
|
||||
Map<String, Object> allowableValues = cm.allowableValues;
|
||||
List<Object> values = (List<Object>) allowableValues.get("values");
|
||||
List<Map<String, Object>> enumVars = buildEnumVars(values, cm.dataType);
|
||||
@@ -1123,7 +1130,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
}
|
||||
} else if (ModelUtils.isMapSchema(s)) {
|
||||
Schema addProps = ModelUtils.getAdditionalProperties(s);
|
||||
if (addProps != null && ModelUtils.isComposedSchema(addProps)) {
|
||||
if (ModelUtils.isComposedSchema(addProps)) {
|
||||
addOneOfNameExtension(addProps, nOneOf);
|
||||
addOneOfInterfaceModel(addProps, nOneOf);
|
||||
}
|
||||
@@ -1157,7 +1164,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
@SuppressWarnings("static-method")
|
||||
public String escapeText(String input) {
|
||||
if (input == null) {
|
||||
return input;
|
||||
return null;
|
||||
}
|
||||
|
||||
// remove \t, \n, \r
|
||||
@@ -1197,7 +1204,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
@Override
|
||||
public String escapeTextWhileAllowingNewLines(String input) {
|
||||
if (input == null) {
|
||||
return input;
|
||||
return null;
|
||||
}
|
||||
|
||||
// remove \t
|
||||
@@ -1209,7 +1216,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
StringEscapeUtils.unescapeJava(
|
||||
StringEscapeUtils.escapeJava(input)
|
||||
.replace("\\/", "/"))
|
||||
.replaceAll("[\\t]", " ")
|
||||
.replaceAll("\\t", " ")
|
||||
.replace("\\", "\\\\")
|
||||
.replace("\"", "\\\""));
|
||||
}
|
||||
@@ -1231,7 +1238,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
@Override
|
||||
public String escapeUnsafeCharacters(String input) {
|
||||
LOGGER.warn("escapeUnsafeCharacters should be overridden in the code generator with proper logic to escape " +
|
||||
"unsafe characters");
|
||||
"unsafe characters");
|
||||
// doing nothing by default and code generator should implement
|
||||
// the logic to prevent code injection
|
||||
// later we'll make this method abstract to make sure
|
||||
@@ -1248,7 +1255,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
@Override
|
||||
public String escapeQuotationMark(String input) {
|
||||
LOGGER.warn("escapeQuotationMark should be overridden in the code generator with proper logic to escape " +
|
||||
"single/double quote");
|
||||
"single/double quote");
|
||||
return input.replace("\"", "\\\"");
|
||||
}
|
||||
|
||||
@@ -2027,35 +2034,35 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
// TODO need to revise how to obtain the example value
|
||||
if (codegenParameter.vendorExtensions != null && codegenParameter.vendorExtensions.containsKey("x-example")) {
|
||||
codegenParameter.example = Json.pretty(codegenParameter.vendorExtensions.get("x-example"));
|
||||
} else if (Boolean.TRUE.equals(codegenParameter.isBoolean)) {
|
||||
} else if (codegenParameter.isBoolean) {
|
||||
codegenParameter.example = "true";
|
||||
} else if (Boolean.TRUE.equals(codegenParameter.isLong)) {
|
||||
} else if (codegenParameter.isLong) {
|
||||
codegenParameter.example = "789";
|
||||
} else if (Boolean.TRUE.equals(codegenParameter.isInteger)) {
|
||||
} else if (codegenParameter.isInteger) {
|
||||
codegenParameter.example = "56";
|
||||
} else if (Boolean.TRUE.equals(codegenParameter.isFloat)) {
|
||||
} else if (codegenParameter.isFloat) {
|
||||
codegenParameter.example = "3.4";
|
||||
} else if (Boolean.TRUE.equals(codegenParameter.isDouble)) {
|
||||
} else if (codegenParameter.isDouble) {
|
||||
codegenParameter.example = "1.2";
|
||||
} else if (Boolean.TRUE.equals(codegenParameter.isNumber)) {
|
||||
} else if (codegenParameter.isNumber) {
|
||||
codegenParameter.example = "8.14";
|
||||
} else if (Boolean.TRUE.equals(codegenParameter.isBinary)) {
|
||||
} else if (codegenParameter.isBinary) {
|
||||
codegenParameter.example = "BINARY_DATA_HERE";
|
||||
} else if (Boolean.TRUE.equals(codegenParameter.isByteArray)) {
|
||||
} else if (codegenParameter.isByteArray) {
|
||||
codegenParameter.example = "BYTE_ARRAY_DATA_HERE";
|
||||
} else if (Boolean.TRUE.equals(codegenParameter.isFile)) {
|
||||
} else if (codegenParameter.isFile) {
|
||||
codegenParameter.example = "/path/to/file.txt";
|
||||
} else if (Boolean.TRUE.equals(codegenParameter.isDate)) {
|
||||
} else if (codegenParameter.isDate) {
|
||||
codegenParameter.example = "2013-10-20";
|
||||
} else if (Boolean.TRUE.equals(codegenParameter.isDateTime)) {
|
||||
} else if (codegenParameter.isDateTime) {
|
||||
codegenParameter.example = "2013-10-20T19:20:30+01:00";
|
||||
} else if (Boolean.TRUE.equals(codegenParameter.isUuid)) {
|
||||
} else if (codegenParameter.isUuid) {
|
||||
codegenParameter.example = "38400000-8cf0-11bd-b23e-10b96e4ef00d";
|
||||
} else if (Boolean.TRUE.equals(codegenParameter.isUri)) {
|
||||
} else if (codegenParameter.isUri) {
|
||||
codegenParameter.example = "https://openapi-generator.tech";
|
||||
} else if (Boolean.TRUE.equals(codegenParameter.isString)) {
|
||||
} else if (codegenParameter.isString) {
|
||||
codegenParameter.example = codegenParameter.paramName + "_example";
|
||||
} else if (Boolean.TRUE.equals(codegenParameter.isFreeFormObject)) {
|
||||
} else if (codegenParameter.isFreeFormObject) {
|
||||
codegenParameter.example = "Object";
|
||||
}
|
||||
|
||||
@@ -2664,10 +2671,10 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
this.schemaIsFromAdditionalProperties = schemaIsFromAdditionalProperties;
|
||||
}
|
||||
|
||||
private String name;
|
||||
private Schema schema;
|
||||
private boolean required;
|
||||
private boolean schemaIsFromAdditionalProperties;
|
||||
private final String name;
|
||||
private final Schema schema;
|
||||
private final boolean required;
|
||||
private final boolean schemaIsFromAdditionalProperties;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
@@ -2677,9 +2684,9 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
return false;
|
||||
NamedSchema that = (NamedSchema) o;
|
||||
return Objects.equals(required, that.required) &&
|
||||
Objects.equals(name, that.name) &&
|
||||
Objects.equals(schema, that.schema) &&
|
||||
Objects.equals(schemaIsFromAdditionalProperties, that.schemaIsFromAdditionalProperties);
|
||||
Objects.equals(name, that.name) &&
|
||||
Objects.equals(schema, that.schema) &&
|
||||
Objects.equals(schemaIsFromAdditionalProperties, that.schemaIsFromAdditionalProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2701,7 +2708,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
if (composed.getProperties() != null && !composed.getProperties().isEmpty()) {
|
||||
if (composed.getOneOf() != null && !composed.getOneOf().isEmpty()) {
|
||||
LOGGER.warn("'oneOf' is intended to include only the additional optional OAS extension discriminator object. " +
|
||||
"For more details, see https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.9.2.1.3 and the OAS section on 'Composition and Inheritance'.");
|
||||
"For more details, see https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.9.2.1.3 and the OAS section on 'Composition and Inheritance'.");
|
||||
}
|
||||
addVars(m, unaliasPropertySchema(composed.getProperties()), composed.getRequired(), null, null);
|
||||
}
|
||||
@@ -2986,7 +2993,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
}
|
||||
LinkedHashMap<String, LinkedHashMap<String, Object>> testNameToTesCase = (LinkedHashMap<String, LinkedHashMap<String, Object>>) schemaNameToTestCases.get(schemaName);
|
||||
for (Entry<String, LinkedHashMap<String, Object>> entry : testNameToTesCase.entrySet()) {
|
||||
LinkedHashMap<String, Object> testExample = (LinkedHashMap<String, Object>) entry.getValue();
|
||||
LinkedHashMap<String, Object> testExample = entry.getValue();
|
||||
String nameInSnakeCase = toTestCaseName(entry.getKey());
|
||||
Object data = processTestExampleData(testExample.get("data"));
|
||||
SchemaTestCase testCase = new SchemaTestCase(
|
||||
@@ -3096,7 +3103,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
m.getVendorExtensions().putAll(schema.getExtensions());
|
||||
}
|
||||
m.isAlias = (typeAliases.containsKey(name)
|
||||
|| isAliasOfSimpleTypes(schema)); // check if the unaliased schema is an alias of simple OAS types
|
||||
|| isAliasOfSimpleTypes(schema)); // check if the unaliased schema is an alias of simple OAS types
|
||||
m.setDiscriminator(createDiscriminator(name, schema));
|
||||
|
||||
if (schema.getDeprecated() != null) {
|
||||
@@ -3466,8 +3473,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
}
|
||||
}
|
||||
if (discriminatorsPropNames.size() > 1) {
|
||||
once(LOGGER).warn("The oneOf schemas have conflicting discriminator property names. " +
|
||||
"oneOf schemas must have the same property name, but found " + String.join(", ", discriminatorsPropNames));
|
||||
once(LOGGER).warn("The oneOf schemas have conflicting discriminator property names. oneOf schemas must have the same property name, but found {}", String.join(", ", discriminatorsPropNames));
|
||||
}
|
||||
if (foundDisc != null && (hasDiscriminatorCnt + hasNullTypeCnt) == composedSchema.getOneOf().size() && discriminatorsPropNames.size() == 1) {
|
||||
disc.setPropertyName(foundDisc.getPropertyName());
|
||||
@@ -3495,8 +3501,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
}
|
||||
}
|
||||
if (discriminatorsPropNames.size() > 1) {
|
||||
once(LOGGER).warn("The anyOf schemas have conflicting discriminator property names. " +
|
||||
"anyOf schemas must have the same property name, but found " + String.join(", ", discriminatorsPropNames));
|
||||
once(LOGGER).warn("The anyOf schemas have conflicting discriminator property names. anyOf schemas must have the same property name, but found {}", String.join(", ", discriminatorsPropNames));
|
||||
}
|
||||
if (foundDisc != null && (hasDiscriminatorCnt + hasNullTypeCnt) == composedSchema.getAnyOf().size() && discriminatorsPropNames.size() == 1) {
|
||||
disc.setPropertyName(foundDisc.getPropertyName());
|
||||
@@ -3550,7 +3555,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
}
|
||||
CodegenProperty df = discriminatorFound(composedSchemaName, sc, discPropName, new TreeSet<String>());
|
||||
String modelName = ModelUtils.getSimpleRef(ref);
|
||||
if (df == null || !df.isString || df.required != true) {
|
||||
if (df == null || !df.isString || !df.required) {
|
||||
String msgSuffix = "";
|
||||
if (df == null) {
|
||||
msgSuffix += discPropName + " is missing from the schema, define it as required and type string";
|
||||
@@ -3558,7 +3563,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
if (!df.isString) {
|
||||
msgSuffix += "invalid type for " + discPropName + ", set it to string";
|
||||
}
|
||||
if (df.required != true) {
|
||||
if (!df.required) {
|
||||
String spacer = "";
|
||||
if (msgSuffix.length() != 0) {
|
||||
spacer = ". ";
|
||||
@@ -3695,13 +3700,13 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
boolean matched = false;
|
||||
for (MappedModel uniqueDescendant : uniqueDescendants) {
|
||||
if (uniqueDescendant.getMappingName().equals(otherDescendant.getMappingName())
|
||||
|| (uniqueDescendant.getModelName().equals(otherDescendant.getModelName()))) {
|
||||
|| (uniqueDescendant.getModelName().equals(otherDescendant.getModelName()))) {
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched == false) {
|
||||
if (!matched) {
|
||||
uniqueDescendants.add(otherDescendant);
|
||||
}
|
||||
}
|
||||
@@ -3893,9 +3898,9 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
}
|
||||
|
||||
property.isNullable = property.isNullable ||
|
||||
!(ModelUtils.isComposedSchema(p)) ||
|
||||
p.getAllOf() == null ||
|
||||
p.getAllOf().size() == 0;
|
||||
!(ModelUtils.isComposedSchema(p)) ||
|
||||
p.getAllOf() == null ||
|
||||
p.getAllOf().size() == 0;
|
||||
if (languageSpecificPrimitives.contains(property.dataType)) {
|
||||
property.isPrimitiveType = true;
|
||||
}
|
||||
@@ -4151,7 +4156,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
if (referencedSchema.getNullable() != null) {
|
||||
property.isNullable = referencedSchema.getNullable();
|
||||
} else if (referencedSchema.getExtensions() != null &&
|
||||
referencedSchema.getExtensions().containsKey(X_NULLABLE)) {
|
||||
referencedSchema.getExtensions().containsKey(X_NULLABLE)) {
|
||||
property.isNullable = (Boolean) referencedSchema.getExtensions().get(X_NULLABLE);
|
||||
}
|
||||
|
||||
@@ -4211,11 +4216,10 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
property.isContainer = true;
|
||||
if (ModelUtils.isSet(p)) {
|
||||
property.containerType = "set";
|
||||
property.containerTypeMapped = typeMapping.get(property.containerType);
|
||||
} else {
|
||||
property.containerType = "array";
|
||||
property.containerTypeMapped = typeMapping.get(property.containerType);
|
||||
}
|
||||
property.containerTypeMapped = typeMapping.get(property.containerType);
|
||||
property.baseType = getSchemaType(p);
|
||||
|
||||
// handle inner property
|
||||
@@ -4235,9 +4239,9 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
}
|
||||
|
||||
boolean isAnyTypeWithNothingElseSet = (ModelUtils.isAnyType(p) &&
|
||||
(p.getProperties() == null || p.getProperties().isEmpty()) &&
|
||||
!ModelUtils.isComposedSchema(p) &&
|
||||
p.getAdditionalProperties() == null && p.getNot() == null && p.getEnum() == null);
|
||||
(p.getProperties() == null || p.getProperties().isEmpty()) &&
|
||||
!ModelUtils.isComposedSchema(p) &&
|
||||
p.getAdditionalProperties() == null && p.getNot() == null && p.getEnum() == null);
|
||||
|
||||
if (!ModelUtils.isArraySchema(p) && !ModelUtils.isMapSchema(p) && !ModelUtils.isFreeFormObject(p, openAPI) && !isAnyTypeWithNothingElseSet) {
|
||||
/* schemas that are not Array, not ModelUtils.isMapSchema, not isFreeFormObject, not AnyType with nothing else set
|
||||
@@ -4356,7 +4360,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
* @param input a set of rules separated by `|`
|
||||
*/
|
||||
void parseDefaultToEmptyContainer(String input) {
|
||||
String[] inputs = ((String) input).split("[|]");
|
||||
String[] inputs = input.split("[|]");
|
||||
String containerType;
|
||||
for (String rule : inputs) {
|
||||
if (StringUtils.isEmpty(rule)) {
|
||||
@@ -4374,7 +4378,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
LOGGER.error("Skipped invalid container type `{}` in `{}`.", containerType, input);
|
||||
}
|
||||
} else if (rule.startsWith("?")) { // nullable (required)
|
||||
containerType = rule.substring(1, rule.length());
|
||||
containerType = rule.substring(1);
|
||||
if ("array".equalsIgnoreCase(containerType)) {
|
||||
arrayNullableDefaultToEmpty = true;
|
||||
} else if ("map".equalsIgnoreCase(containerType)) {
|
||||
@@ -4492,8 +4496,8 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
|
||||
protected CodegenProperty getMostInnerItems(CodegenProperty property) {
|
||||
CodegenProperty currentProperty = property;
|
||||
while (currentProperty != null && (Boolean.TRUE.equals(currentProperty.isMap)
|
||||
|| Boolean.TRUE.equals(currentProperty.isArray)) && currentProperty.items != null) {
|
||||
while (currentProperty != null && (currentProperty.isMap
|
||||
|| currentProperty.isArray) && currentProperty.items != null) {
|
||||
currentProperty = currentProperty.items;
|
||||
}
|
||||
return currentProperty;
|
||||
@@ -4512,8 +4516,8 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
*/
|
||||
protected void updateDataTypeWithEnumForArray(CodegenProperty property) {
|
||||
CodegenProperty baseItem = property.items;
|
||||
while (baseItem != null && (Boolean.TRUE.equals(baseItem.isMap)
|
||||
|| Boolean.TRUE.equals(baseItem.isArray))) {
|
||||
while (baseItem != null && (baseItem.isMap
|
||||
|| baseItem.isArray)) {
|
||||
baseItem = baseItem.items;
|
||||
}
|
||||
if (baseItem != null) {
|
||||
@@ -4540,8 +4544,8 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
*/
|
||||
protected void updateDataTypeWithEnumForMap(CodegenProperty property) {
|
||||
CodegenProperty baseItem = property.items;
|
||||
while (baseItem != null && (Boolean.TRUE.equals(baseItem.isMap)
|
||||
|| Boolean.TRUE.equals(baseItem.isArray))) {
|
||||
while (baseItem != null && (baseItem.isMap
|
||||
|| baseItem.isArray)) {
|
||||
baseItem = baseItem.items;
|
||||
}
|
||||
|
||||
@@ -4602,9 +4606,9 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
* @param methodResponse the default ApiResponse for the endpoint
|
||||
*/
|
||||
protected void handleMethodResponse(Operation operation,
|
||||
Map<String, Schema> schemas,
|
||||
CodegenOperation op,
|
||||
ApiResponse methodResponse) {
|
||||
Map<String, Schema> schemas,
|
||||
CodegenOperation op,
|
||||
ApiResponse methodResponse) {
|
||||
handleMethodResponse(operation, schemas, op, methodResponse, Collections.emptyMap());
|
||||
}
|
||||
|
||||
@@ -4618,10 +4622,10 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
* @param schemaMappings mappings of external types to be omitted by unaliasing
|
||||
*/
|
||||
protected void handleMethodResponse(Operation operation,
|
||||
Map<String, Schema> schemas,
|
||||
CodegenOperation op,
|
||||
ApiResponse methodResponse,
|
||||
Map<String, String> schemaMappings) {
|
||||
Map<String, Schema> schemas,
|
||||
CodegenOperation op,
|
||||
ApiResponse methodResponse,
|
||||
Map<String, String> schemaMappings) {
|
||||
ApiResponse response = ModelUtils.getReferencedApiResponse(openAPI, methodResponse);
|
||||
Schema responseSchema = unaliasSchema(ModelUtils.getSchemaFromResponse(openAPI, response));
|
||||
|
||||
@@ -4690,9 +4694,9 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
*/
|
||||
@Override
|
||||
public CodegenOperation fromOperation(String path,
|
||||
String httpMethod,
|
||||
Operation operation,
|
||||
List<Server> servers) {
|
||||
String httpMethod,
|
||||
Operation operation,
|
||||
List<Server> servers) {
|
||||
LOGGER.debug("fromOperation => operation: {}", operation);
|
||||
if (operation == null)
|
||||
throw new RuntimeException("operation cannot be null in fromOperation");
|
||||
@@ -4760,8 +4764,8 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
r.setContent(getContent(response.getContent(), imports, mediaTypeSchemaSuffix));
|
||||
|
||||
if (r.baseType != null &&
|
||||
!defaultIncludes.contains(r.baseType) &&
|
||||
!languageSpecificPrimitives.contains(r.baseType)) {
|
||||
!defaultIncludes.contains(r.baseType) &&
|
||||
!languageSpecificPrimitives.contains(r.baseType)) {
|
||||
imports.add(r.baseType);
|
||||
}
|
||||
|
||||
@@ -4771,26 +4775,26 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
}
|
||||
|
||||
op.responses.add(r);
|
||||
if (Boolean.TRUE.equals(r.isBinary) && Boolean.TRUE.equals(r.is2xx) && Boolean.FALSE.equals(op.isResponseBinary)) {
|
||||
if (r.isBinary && r.is2xx && !op.isResponseBinary) {
|
||||
op.isResponseBinary = Boolean.TRUE;
|
||||
}
|
||||
if (Boolean.TRUE.equals(r.isFile) && Boolean.TRUE.equals(r.is2xx) && Boolean.FALSE.equals(op.isResponseFile)) {
|
||||
if (r.isFile && r.is2xx && !op.isResponseFile) {
|
||||
op.isResponseFile = Boolean.TRUE;
|
||||
}
|
||||
if (Boolean.TRUE.equals(r.isDefault)) {
|
||||
if (r.isDefault) {
|
||||
op.defaultReturnType = Boolean.TRUE;
|
||||
}
|
||||
|
||||
// check if any 4xx or 5xx response has an error response object defined
|
||||
if ((Boolean.TRUE.equals(r.is4xx) || Boolean.TRUE.equals(r.is5xx)) &&
|
||||
Boolean.FALSE.equals(r.primitiveType) && Boolean.FALSE.equals(r.simpleType)) {
|
||||
if ((r.is4xx || r.is5xx) &&
|
||||
!r.primitiveType && !r.simpleType) {
|
||||
op.hasErrorResponseObject = Boolean.TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// check if the operation can both return a 2xx response with a body and without
|
||||
if (op.responses.stream().anyMatch(response -> response.is2xx && response.dataType != null) &&
|
||||
op.responses.stream().anyMatch(response -> response.is2xx && response.dataType == null)) {
|
||||
op.responses.stream().anyMatch(response -> response.is2xx && response.dataType == null)) {
|
||||
op.isResponseOptional = Boolean.TRUE;
|
||||
}
|
||||
|
||||
@@ -4863,8 +4867,8 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
contentType = contentType.toLowerCase(Locale.ROOT);
|
||||
}
|
||||
if (contentType != null &&
|
||||
((!(this instanceof RustAxumServerCodegen) && contentType.startsWith("application/x-www-form-urlencoded")) ||
|
||||
contentType.startsWith("multipart"))) {
|
||||
((!(this instanceof RustAxumServerCodegen) && contentType.startsWith("application/x-www-form-urlencoded")) ||
|
||||
contentType.startsWith("multipart"))) {
|
||||
// process form parameters
|
||||
formParams = fromRequestBodyToFormParameters(requestBody, imports);
|
||||
op.isMultipart = contentType.startsWith("multipart");
|
||||
@@ -5054,23 +5058,23 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
r.code = responseCode;
|
||||
switch (r.code.charAt(0)) {
|
||||
|
||||
case '1':
|
||||
r.is1xx = true;
|
||||
break;
|
||||
case '2':
|
||||
r.is2xx = true;
|
||||
break;
|
||||
case '3':
|
||||
r.is3xx = true;
|
||||
break;
|
||||
case '4':
|
||||
r.is4xx = true;
|
||||
break;
|
||||
case '5':
|
||||
r.is5xx = true;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Invalid response code " + responseCode);
|
||||
case '1':
|
||||
r.is1xx = true;
|
||||
break;
|
||||
case '2':
|
||||
r.is2xx = true;
|
||||
break;
|
||||
case '3':
|
||||
r.is3xx = true;
|
||||
break;
|
||||
case '4':
|
||||
r.is4xx = true;
|
||||
break;
|
||||
case '5':
|
||||
r.is5xx = true;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Invalid response code " + responseCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5341,7 +5345,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
codegenParameter.isDecimal = true;
|
||||
codegenParameter.isPrimitiveType = true;
|
||||
}
|
||||
if (Boolean.TRUE.equals(codegenParameter.isString)) {
|
||||
if (codegenParameter.isString) {
|
||||
codegenParameter.isPrimitiveType = true;
|
||||
}
|
||||
}
|
||||
@@ -5534,7 +5538,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
}
|
||||
|
||||
CodegenProperty codegenProperty = fromProperty(parameter.getName(), parameterSchema, false);
|
||||
if (Boolean.TRUE.equals(codegenProperty.isModel)) {
|
||||
if (codegenProperty.isModel) {
|
||||
codegenParameter.isModel = true;
|
||||
}
|
||||
|
||||
@@ -5879,7 +5883,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
*/
|
||||
protected boolean needToImport(String type) {
|
||||
return StringUtils.isNotBlank(type) && !defaultIncludes.contains(type)
|
||||
&& !languageSpecificPrimitives.contains(type);
|
||||
&& !languageSpecificPrimitives.contains(type);
|
||||
}
|
||||
|
||||
@SuppressWarnings("static-method")
|
||||
@@ -6014,8 +6018,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
* @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);
|
||||
Matcher matcher = TRAILING_DIGITS.matcher(name);
|
||||
if (matcher.find()) {
|
||||
String numStr = matcher.group();
|
||||
int num = Integer.parseInt(numStr) + 1;
|
||||
@@ -6088,7 +6091,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
}
|
||||
|
||||
protected void addVars(CodegenModel m, Map<String, Schema> properties, List<String> required,
|
||||
Map<String, Schema> allProperties, List<String> allRequired) {
|
||||
Map<String, Schema> allProperties, List<String> allRequired) {
|
||||
|
||||
m.hasRequired = false;
|
||||
m.hasReadOnly = false;
|
||||
@@ -6169,7 +6172,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
} else {
|
||||
final CodegenProperty cp;
|
||||
|
||||
if (cm != null && cm.allVars == vars && varsMap.keySet().contains(key)) {
|
||||
if (cm != null && cm.allVars == vars && varsMap.containsKey(key)) {
|
||||
// when updating allVars, reuse the codegen property from the child model if it's already present
|
||||
// the goal is to avoid issues when the property is defined in both child, parent but the
|
||||
// definition is not identical, e.g. required vs optional, integer vs string
|
||||
@@ -6204,18 +6207,18 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
}
|
||||
|
||||
// set model's hasOnlyReadOnly to false if the property is read-only
|
||||
if (!Boolean.TRUE.equals(cp.isReadOnly)) {
|
||||
if (!cp.isReadOnly) {
|
||||
cm.hasOnlyReadOnly = false;
|
||||
}
|
||||
addImportsForPropertyType(cm, cp);
|
||||
|
||||
// if required, add to the list "requiredVars"
|
||||
if (Boolean.FALSE.equals(cp.required)) {
|
||||
if (!cp.required) {
|
||||
cm.optionalVars.add(cp);
|
||||
}
|
||||
|
||||
// if readonly, add to readOnlyVars (list of properties)
|
||||
if (Boolean.TRUE.equals(cp.isReadOnly)) {
|
||||
if (cp.isReadOnly) {
|
||||
cm.readOnlyVars.add(cp);
|
||||
cm.hasReadOnly = true;
|
||||
} else { // else add to readWriteVars (list of properties)
|
||||
@@ -6223,12 +6226,11 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
cm.readWriteVars.add(cp);
|
||||
}
|
||||
|
||||
if (Boolean.FALSE.equals(cp.isNullable)) {
|
||||
if (!cp.isNullable) {
|
||||
cm.nonNullableVars.add(cp);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6291,7 +6293,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
|
||||
// allOf with a single item
|
||||
if (schema.getAllOf() != null && schema.getAllOf().size() == 1
|
||||
&& schema.getAllOf().get(0) instanceof Schema) {
|
||||
&& schema.getAllOf().get(0) instanceof Schema) {
|
||||
schema = unaliasSchema((Schema) schema.getAllOf().get(0));
|
||||
schema = ModelUtils.getReferencedSchema(this.openAPI, schema);
|
||||
}
|
||||
@@ -6337,16 +6339,19 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
* Not all operating systems support case-sensitive paths
|
||||
*/
|
||||
private String uniqueCaseInsensitiveString(String value, Map<String, String> seenValues) {
|
||||
if (seenValues.keySet().contains(value)) {
|
||||
if (seenValues.containsKey(value)) {
|
||||
return seenValues.get(value);
|
||||
}
|
||||
|
||||
Optional<Entry<String, String>> foundEntry = seenValues.entrySet().stream().filter(v -> v.getValue().toLowerCase(Locale.ROOT).equals(value.toLowerCase(Locale.ROOT))).findAny();
|
||||
if (foundEntry.isPresent()) {
|
||||
// Build the set of already-used lowercase values once, to avoid O(n) re-collection per loop iteration.
|
||||
Set<String> lowercaseValues = seenValues.values().stream()
|
||||
.map(v -> v.toLowerCase(Locale.ROOT))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (lowercaseValues.contains(value.toLowerCase(Locale.ROOT))) {
|
||||
int counter = 0;
|
||||
String uniqueValue = value + "_" + counter;
|
||||
|
||||
while (seenValues.values().stream().map(v -> v.toLowerCase(Locale.ROOT)).collect(Collectors.toList()).contains(uniqueValue.toLowerCase(Locale.ROOT))) {
|
||||
while (lowercaseValues.contains(uniqueValue.toLowerCase(Locale.ROOT))) {
|
||||
counter++;
|
||||
uniqueValue = value + "_" + counter;
|
||||
}
|
||||
@@ -6758,7 +6763,10 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
// remove everything else other than word, number and _
|
||||
// $php_variable => php_variable
|
||||
if (allowUnicodeIdentifiers) { //could be converted to a single line with ?: operator
|
||||
modifiable = Pattern.compile(sanitizeNameOptions.getRemoveCharRegEx(), Pattern.UNICODE_CHARACTER_CLASS).matcher(modifiable).replaceAll("");
|
||||
modifiable = REMOVE_CHAR_UNICODE_PATTERN_CACHE
|
||||
.computeIfAbsent(sanitizeNameOptions.getRemoveCharRegEx(),
|
||||
regex -> Pattern.compile(regex, Pattern.UNICODE_CHARACTER_CLASS))
|
||||
.matcher(modifiable).replaceAll("");
|
||||
} else {
|
||||
modifiable = modifiable.replaceAll(sanitizeNameOptions.getRemoveCharRegEx(), "");
|
||||
}
|
||||
@@ -6767,7 +6775,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
}
|
||||
|
||||
private String sanitizeValue(String value, String replaceMatch, String replaceValue, List<String> exceptionList) {
|
||||
if (exceptionList.size() == 0 || !exceptionList.contains(replaceMatch)) {
|
||||
if (exceptionList.isEmpty() || !exceptionList.contains(replaceMatch)) {
|
||||
return value.replaceAll(replaceMatch, replaceValue);
|
||||
}
|
||||
return value;
|
||||
@@ -6810,65 +6818,65 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
LOGGER.error("Codegen Property cannot be null.");
|
||||
return;
|
||||
}
|
||||
if (Boolean.TRUE.equals(property.isEmail) && Boolean.TRUE.equals(property.isString)) {
|
||||
if (property.isEmail && property.isString) {
|
||||
parameter.isEmail = true;
|
||||
} else if (Boolean.TRUE.equals(property.isPassword) && Boolean.TRUE.equals(property.isString)) {
|
||||
} else if (property.isPassword && property.isString) {
|
||||
parameter.isPassword = true;
|
||||
} else if (Boolean.TRUE.equals(property.isUuid) && Boolean.TRUE.equals(property.isString)) {
|
||||
} else if (property.isUuid && property.isString) {
|
||||
parameter.isUuid = true;
|
||||
} else if (Boolean.TRUE.equals(property.isByteArray)) {
|
||||
} else if (property.isByteArray) {
|
||||
parameter.isByteArray = true;
|
||||
parameter.isPrimitiveType = true;
|
||||
} else if (Boolean.TRUE.equals(property.isBinary)) {
|
||||
} else if (property.isBinary) {
|
||||
parameter.isBinary = true;
|
||||
parameter.isPrimitiveType = true;
|
||||
} else if (Boolean.TRUE.equals(property.isString)) {
|
||||
} else if (property.isString) {
|
||||
parameter.isString = true;
|
||||
parameter.isPrimitiveType = true;
|
||||
} else if (Boolean.TRUE.equals(property.isBoolean)) {
|
||||
} else if (property.isBoolean) {
|
||||
parameter.isBoolean = true;
|
||||
parameter.isPrimitiveType = true;
|
||||
} else if (Boolean.TRUE.equals(property.isLong)) {
|
||||
} else if (property.isLong) {
|
||||
parameter.isLong = true;
|
||||
parameter.isPrimitiveType = true;
|
||||
} else if (Boolean.TRUE.equals(property.isInteger)) {
|
||||
} else if (property.isInteger) {
|
||||
parameter.isInteger = true;
|
||||
parameter.isPrimitiveType = true;
|
||||
if (Boolean.TRUE.equals(property.isShort)) {
|
||||
if (property.isShort) {
|
||||
parameter.isShort = true;
|
||||
} else if (Boolean.TRUE.equals(property.isUnboundedInteger)) {
|
||||
} else if (property.isUnboundedInteger) {
|
||||
parameter.isUnboundedInteger = true;
|
||||
}
|
||||
} else if (Boolean.TRUE.equals(property.isDouble)) {
|
||||
} else if (property.isDouble) {
|
||||
parameter.isDouble = true;
|
||||
parameter.isPrimitiveType = true;
|
||||
} else if (Boolean.TRUE.equals(property.isFloat)) {
|
||||
} else if (property.isFloat) {
|
||||
parameter.isFloat = true;
|
||||
parameter.isPrimitiveType = true;
|
||||
} else if (Boolean.TRUE.equals(property.isDecimal)) {
|
||||
} else if (property.isDecimal) {
|
||||
parameter.isDecimal = true;
|
||||
parameter.isPrimitiveType = true;
|
||||
} else if (Boolean.TRUE.equals(property.isNumber)) {
|
||||
} else if (property.isNumber) {
|
||||
parameter.isNumber = true;
|
||||
parameter.isPrimitiveType = true;
|
||||
} else if (Boolean.TRUE.equals(property.isDate)) {
|
||||
} else if (property.isDate) {
|
||||
parameter.isDate = true;
|
||||
parameter.isPrimitiveType = true;
|
||||
} else if (Boolean.TRUE.equals(property.isDateTime)) {
|
||||
} else if (property.isDateTime) {
|
||||
parameter.isDateTime = true;
|
||||
parameter.isPrimitiveType = true;
|
||||
} else if (Boolean.TRUE.equals(property.isFreeFormObject)) {
|
||||
} else if (property.isFreeFormObject) {
|
||||
parameter.isFreeFormObject = true;
|
||||
} else if (Boolean.TRUE.equals(property.isAnyType)) {
|
||||
} else if (property.isAnyType) {
|
||||
parameter.isAnyType = true;
|
||||
} else {
|
||||
LOGGER.debug("Property type is not primitive: {}", property.dataType);
|
||||
}
|
||||
|
||||
if (Boolean.TRUE.equals(property.isFile)) {
|
||||
if (property.isFile) {
|
||||
parameter.isFile = true;
|
||||
}
|
||||
if (Boolean.TRUE.equals(property.isModel)) {
|
||||
if (property.isModel) {
|
||||
parameter.isModel = true;
|
||||
}
|
||||
}
|
||||
@@ -7002,7 +7010,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
long count = enumVars.stream().filter(v1 -> v1.get("name").equals(name)).count();
|
||||
if (count > 1) {
|
||||
String uniqueEnumName = getUniqueEnumName(name, enumVars);
|
||||
LOGGER.debug("Changing duplicate enumeration name from " + v.get("name") + " to " + uniqueEnumName);
|
||||
LOGGER.debug("Changing duplicate enumeration name from {} to {}", v.get("name"), uniqueEnumName);
|
||||
v.put("name", uniqueEnumName);
|
||||
}
|
||||
});
|
||||
@@ -7306,8 +7314,8 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
|
||||
for (String consume : consumesInfo) {
|
||||
if (consume != null &&
|
||||
(consume.toLowerCase(Locale.ROOT).startsWith("application/x-www-form-urlencoded") ||
|
||||
consume.toLowerCase(Locale.ROOT).startsWith("multipart"))) {
|
||||
(consume.toLowerCase(Locale.ROOT).startsWith("application/x-www-form-urlencoded") ||
|
||||
consume.toLowerCase(Locale.ROOT).startsWith("multipart"))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -7426,7 +7434,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
Schema original = null;
|
||||
// check if it's allOf (only 1 sub schema) with or without default/nullable/etc set in the top level
|
||||
if (ModelUtils.isAllOf(schema) && schema.getAllOf().size() == 1 &&
|
||||
(ModelUtils.getType(schema) == null || "object".equals(ModelUtils.getType(schema)))) {
|
||||
(ModelUtils.getType(schema) == null || "object".equals(ModelUtils.getType(schema)))) {
|
||||
if (schema.getAllOf().get(0) instanceof Schema) {
|
||||
original = schema;
|
||||
schema = (Schema) schema.getAllOf().get(0);
|
||||
@@ -7547,7 +7555,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
codegenParameter.isDecimal = true;
|
||||
codegenParameter.isPrimitiveType = true;
|
||||
}
|
||||
if (Boolean.TRUE.equals(codegenParameter.isString)) {
|
||||
if (codegenParameter.isString) {
|
||||
codegenParameter.isPrimitiveType = true;
|
||||
}
|
||||
} else if (ModelUtils.isBooleanSchema(ps)) {
|
||||
@@ -7577,7 +7585,6 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
codegenParameter.setAdditionalPropertiesIsAnyType(codegenProperty.getAdditionalPropertiesIsAnyType());
|
||||
codegenParameter.items = codegenProperty.items;
|
||||
codegenParameter.isPrimitiveType = false;
|
||||
codegenParameter.items = codegenProperty.items;
|
||||
codegenParameter.mostInnerItems = codegenProperty.mostInnerItems;
|
||||
} else if (ModelUtils.isFreeFormObject(ps, openAPI)) {
|
||||
codegenParameter.isFreeFormObject = true;
|
||||
@@ -7630,7 +7637,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
// referenced schemas
|
||||
}
|
||||
|
||||
if (Boolean.TRUE.equals(codegenProperty.isModel)) {
|
||||
if (codegenProperty.isModel) {
|
||||
codegenParameter.isModel = true;
|
||||
}
|
||||
|
||||
@@ -7718,9 +7725,9 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
codegenModelDescription = codegenModel.description;
|
||||
} else {
|
||||
LOGGER.warn("The following schema has undefined (null) baseType. " +
|
||||
"It could be due to form parameter defined in OpenAPI v2 spec with incorrect consumes. " +
|
||||
"A correct 'consumes' for form parameters should be " +
|
||||
"'application/x-www-form-urlencoded' or 'multipart/?'");
|
||||
"It could be due to form parameter defined in OpenAPI v2 spec with incorrect consumes. " +
|
||||
"A correct 'consumes' for form parameters should be " +
|
||||
"'application/x-www-form-urlencoded' or 'multipart/?'");
|
||||
LOGGER.warn("schema: {}", schema);
|
||||
LOGGER.warn("codegenModel is null. Default to UNKNOWN_BASE_TYPE");
|
||||
codegenModelName = "UNKNOWN_BASE_TYPE";
|
||||
@@ -8002,8 +8009,8 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
enc.getContentType(),
|
||||
headers,
|
||||
enc.getStyle().toString(),
|
||||
enc.getExplode() == null ? false : enc.getExplode().booleanValue(),
|
||||
enc.getAllowReserved() == null ? false : enc.getAllowReserved().booleanValue()
|
||||
enc.getExplode() != null && enc.getExplode(),
|
||||
enc.getAllowReserved() != null && enc.getAllowReserved()
|
||||
);
|
||||
|
||||
if (enc.getExtensions() != null) {
|
||||
@@ -8078,7 +8085,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
Schema original = null;
|
||||
// check if it's allOf (only 1 sub schema) with or without default/nullable/etc set in the top level
|
||||
if (ModelUtils.isAllOf(schema) && schema.getAllOf().size() == 1 &&
|
||||
(ModelUtils.getType(schema) == null || "object".equals(ModelUtils.getType(schema)))) {
|
||||
(ModelUtils.getType(schema) == null || "object".equals(ModelUtils.getType(schema)))) {
|
||||
if (schema.getAllOf().get(0) instanceof Schema) {
|
||||
original = schema;
|
||||
schema = (Schema) schema.getAllOf().get(0);
|
||||
@@ -8230,9 +8237,8 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found == false) {
|
||||
if (!found) {
|
||||
LOGGER.warn("Property {} is not processed correctly (missing from getVars). Maybe it's a const (not yet supported) in openapi v3.1 spec.", requiredPropertyName);
|
||||
continue;
|
||||
}
|
||||
} else if (schema.getAdditionalProperties() instanceof Boolean && Boolean.FALSE.equals(schema.getAdditionalProperties())) {
|
||||
// TODO add processing for requiredPropertyName
|
||||
@@ -8466,7 +8472,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
int exitValue = p.exitValue();
|
||||
if (exitValue != 0) {
|
||||
try (InputStreamReader inputStreamReader = new InputStreamReader(p.getErrorStream(), StandardCharsets.UTF_8);
|
||||
BufferedReader br = new BufferedReader(inputStreamReader)) {
|
||||
BufferedReader br = new BufferedReader(inputStreamReader)) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
@@ -8577,7 +8583,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
* @param name name of the parent oneOf schema
|
||||
*/
|
||||
public void addOneOfNameExtension(Schema schema, String name) {
|
||||
if (schema.getOneOf() != null && schema.getOneOf().size() > 0) {
|
||||
if (schema.getOneOf() != null && !schema.getOneOf().isEmpty()) {
|
||||
schema.addExtension(X_ONE_OF_NAME, name);
|
||||
}
|
||||
}
|
||||
@@ -8658,8 +8664,8 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
return false;
|
||||
SanitizeNameOptions that = (SanitizeNameOptions) o;
|
||||
return Objects.equals(getName(), that.getName()) &&
|
||||
Objects.equals(getRemoveCharRegEx(), that.getRemoveCharRegEx()) &&
|
||||
Objects.equals(getExceptions(), that.getExceptions());
|
||||
Objects.equals(getRemoveCharRegEx(), that.getRemoveCharRegEx()) &&
|
||||
Objects.equals(getExceptions(), that.getExceptions());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -8765,7 +8771,7 @@ public class DefaultCodegen implements CodegenConfig {
|
||||
i += 1;
|
||||
|
||||
if (dataTypeSet.contains(cp.dataType)
|
||||
|| (isTypeErasedGenerics() && dataTypeSet.contains(cp.baseType))) {
|
||||
|| (isTypeErasedGenerics() && dataTypeSet.contains(cp.baseType))) {
|
||||
// add "x-duplicated-data-type" to indicate if the (base) dataType already occurs before
|
||||
// in other sub-schemas of allOf/anyOf/oneOf
|
||||
cp.vendorExtensions.putIfAbsent("x-duplicated-data-type", true);
|
||||
|
||||
+20
-20
@@ -86,16 +86,17 @@ public class DefaultGenerator implements Generator {
|
||||
private String basePath;
|
||||
private String basePathWithoutHost;
|
||||
private String contextPath;
|
||||
private Map<String, String> generatorPropertyDefaults = new HashMap<>();
|
||||
private final Map<String, String> generatorPropertyDefaults = new HashMap<>();
|
||||
/**
|
||||
* Retrieves an instance to the configured template processor, available after user-defined options are
|
||||
* applied via
|
||||
*/
|
||||
@Getter protected TemplateProcessor templateProcessor = null;
|
||||
@Getter
|
||||
protected TemplateProcessor templateProcessor = null;
|
||||
|
||||
private List<TemplateDefinition> userDefinedTemplates = new ArrayList<>();
|
||||
private String generatorCheck = "spring";
|
||||
private String templateCheck = "apiController.mustache";
|
||||
private final String generatorCheck = "spring";
|
||||
private final String templateCheck = "apiController.mustache";
|
||||
|
||||
|
||||
public DefaultGenerator() {
|
||||
@@ -266,8 +267,7 @@ public class DefaultGenerator implements Generator {
|
||||
openapiNormalizer.normalize();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("An exception occurred in OpenAPI Normalizer. Please report the issue via https://github.com/openapitools/openapi-generator/issues/new/: ");
|
||||
e.printStackTrace();
|
||||
LOGGER.error("An exception occurred in OpenAPI Normalizer. Please report the issue via https://github.com/openapitools/openapi-generator/issues/new/: ", e);
|
||||
}
|
||||
|
||||
// resolve inline models
|
||||
@@ -607,10 +607,10 @@ public class DefaultGenerator implements Generator {
|
||||
if (!processedModels.contains(key) && allSchemas.containsKey(key)) {
|
||||
generateModels(files, allModels, unusedModels, aliasModels, processedModels, () -> Set.of(key));
|
||||
} else {
|
||||
LOGGER.info("Type " + variable.getComplexType() + " of variable " + variable.getName() + " could not be resolve because it is not declared as a model.");
|
||||
LOGGER.info("Type {} of variable {} could not be resolve because it is not declared as a model.", variable.getComplexType(), variable.getName());
|
||||
}
|
||||
} else {
|
||||
LOGGER.info("Type " + variable.getOpenApiType() + " of variable " + variable.getName() + " could not be resolve because it is not declared as a model.");
|
||||
LOGGER.info("Type {} of variable {} could not be resolve because it is not declared as a model.", variable.getOpenApiType(), variable.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -639,8 +639,8 @@ public class DefaultGenerator implements Generator {
|
||||
}
|
||||
|
||||
return Arrays.stream(propertyRaw.split(","))
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toSet());
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private Set<String> modelKeys() {
|
||||
@@ -665,7 +665,6 @@ public class DefaultGenerator implements Generator {
|
||||
return modelKeys;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void generateApis(List<File> files, List<OperationsMap> allOperations, List<ModelMap> allModels) {
|
||||
if (!generateApis) {
|
||||
// TODO: Process these anyway and present info via dryRun?
|
||||
@@ -1006,7 +1005,7 @@ public class DefaultGenerator implements Generator {
|
||||
File ignoreFile = new File(ignoreFileNameTarget);
|
||||
// use the entries provided by the users to pre-populate .openapi-generator-ignore
|
||||
try {
|
||||
LOGGER.info("Writing file " + ignoreFileNameTarget + " (which is always overwritten when the option `openapiGeneratorIgnoreFile` is enabled.)");
|
||||
LOGGER.info("Writing file {} (which is always overwritten when the option `openapiGeneratorIgnoreFile` is enabled.)", ignoreFileNameTarget);
|
||||
new File(config.outputFolder()).mkdirs();
|
||||
if (!ignoreFile.createNewFile()) {
|
||||
// file may already exist, do nothing
|
||||
@@ -1430,7 +1429,10 @@ public class DefaultGenerator implements Generator {
|
||||
return processTemplateToFile(templateData, templateName, outputFilename, shouldGenerate, skippedByOption, this.config.getOutputDir());
|
||||
}
|
||||
|
||||
private final Set<String> seenFiles = new HashSet<>();
|
||||
/**
|
||||
* Stores lowercased absolute paths for O(1) case-insensitive duplicate detection.
|
||||
*/
|
||||
private final Set<String> seenFilesLower = new HashSet<>();
|
||||
|
||||
private File processTemplateToFile(Map<String, Object> templateData, String templateName, String outputFilename, boolean shouldGenerate, String skippedByOption, String intendedOutputDir) throws IOException {
|
||||
String adjustedOutputFilename = outputFilename.replaceAll("//", "/").replace('/', File.separatorChar);
|
||||
@@ -1443,10 +1445,10 @@ public class DefaultGenerator implements Generator {
|
||||
throw new RuntimeException(String.format(Locale.ROOT, "Target files must be generated within the output directory; absoluteTarget=%s outDir=%s", absoluteTarget, outDir));
|
||||
}
|
||||
|
||||
if (seenFiles.stream().filter(f -> f.toLowerCase(Locale.ROOT).equals(absoluteTarget.toString().toLowerCase(Locale.ROOT))).findAny().isPresent()) {
|
||||
LOGGER.warn("Duplicate file path detected. Not all operating systems can handle case sensitive file paths. path={}", absoluteTarget.toString());
|
||||
// O(1) case-insensitive duplicate check via a pre-lowercased shadow set
|
||||
if (!seenFilesLower.add(absoluteTarget.toString().toLowerCase(Locale.ROOT))) {
|
||||
LOGGER.warn("Duplicate file path detected. Not all operating systems can handle case sensitive file paths. path={}", absoluteTarget);
|
||||
}
|
||||
seenFiles.add(absoluteTarget.toString());
|
||||
return this.templateProcessor.write(templateData, templateName, target);
|
||||
} else {
|
||||
this.templateProcessor.skip(target.toPath(), String.format(Locale.ROOT, "Skipped by %s options supplied by user.", skippedByOption));
|
||||
@@ -2002,10 +2004,8 @@ public class DefaultGenerator implements Generator {
|
||||
}
|
||||
});
|
||||
|
||||
Collections.sort(relativePaths, (a, b) -> IOCase.SENSITIVE.checkCompareTo(a, b));
|
||||
relativePaths.forEach(relativePath -> {
|
||||
sb.append(relativePath).append(System.lineSeparator());
|
||||
});
|
||||
relativePaths.sort(IOCase.SENSITIVE::checkCompareTo);
|
||||
relativePaths.forEach(relativePath -> sb.append(relativePath).append(System.lineSeparator()));
|
||||
|
||||
String targetFile = config.outputFolder() + File.separator + METADATA_DIR + File.separator + config.getFilesMetadataFilename();
|
||||
|
||||
|
||||
+18
-4
@@ -21,6 +21,7 @@ import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
@@ -33,6 +34,9 @@ public class TemplateManager implements TemplatingExecutor, TemplateProcessor {
|
||||
|
||||
private final Logger LOGGER = LoggerFactory.getLogger(TemplateManager.class);
|
||||
|
||||
/** Cache of resolved template path -> raw template content, populated on first read per run. */
|
||||
private final Map<String, String> templateContentCache = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs a new instance of a {@link TemplateManager}
|
||||
*
|
||||
@@ -75,7 +79,8 @@ public class TemplateManager implements TemplatingExecutor, TemplateProcessor {
|
||||
*/
|
||||
@Override
|
||||
public String getFullTemplateContents(String name) {
|
||||
return readTemplate(getFullTemplateFile(name));
|
||||
String fullPath = getFullTemplateFile(name);
|
||||
return templateContentCache.computeIfAbsent(fullPath, this::readTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,6 +94,13 @@ public class TemplateManager implements TemplatingExecutor, TemplateProcessor {
|
||||
return Paths.get(getFullTemplateFile(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-compiled pattern for replacing the OS file separator with '/' in classpath resource paths.
|
||||
* Only non-null on operating systems where {@link File#separator} is not already '/'.
|
||||
*/
|
||||
private static final Pattern FILE_SEP_PATTERN =
|
||||
"/".equals(File.separator) ? null : Pattern.compile(Pattern.quote(File.separator));
|
||||
|
||||
/**
|
||||
* Gets a normalized classpath resource location according to OS-specific file separator
|
||||
*
|
||||
@@ -96,8 +108,8 @@ public class TemplateManager implements TemplatingExecutor, TemplateProcessor {
|
||||
* @return A normalized string according to OS-specific file separator
|
||||
*/
|
||||
public static String getCPResourcePath(final String name) {
|
||||
if (!"/".equals(File.separator)) {
|
||||
return name.replaceAll(Pattern.quote(File.separator), "/");
|
||||
if (FILE_SEP_PATTERN != null) {
|
||||
return FILE_SEP_PATTERN.matcher(name).replaceAll("/");
|
||||
}
|
||||
return name;
|
||||
}
|
||||
@@ -262,6 +274,8 @@ public class TemplateManager implements TemplatingExecutor, TemplateProcessor {
|
||||
}
|
||||
|
||||
private boolean filesEqual(File file1, File file2) throws IOException {
|
||||
return file1.exists() && file2.exists() && Arrays.equals(Files.readAllBytes(file1.toPath()), Files.readAllBytes(file2.toPath()));
|
||||
if (!file1.exists() || !file2.exists()) return false;
|
||||
if (file1.length() != file2.length()) return false;
|
||||
return Arrays.equals(Files.readAllBytes(file1.toPath()), Files.readAllBytes(file2.toPath()));
|
||||
}
|
||||
}
|
||||
|
||||
+23
-1
@@ -7,6 +7,8 @@ import org.openapitools.codegen.api.TemplatePathLocator;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Locates templates according to {@link CodegenConfig} settings.
|
||||
@@ -14,6 +16,13 @@ import java.nio.file.Paths;
|
||||
public class GeneratorTemplateContentLocator implements TemplatePathLocator {
|
||||
private final CodegenConfig codegenConfig;
|
||||
|
||||
/**
|
||||
* Cache of relativeTemplateFile -> resolved full path (or empty Optional when the template does not exist).
|
||||
* The filesystem/classpath existence probes inside resolveFullTemplatePath are expensive on repeated calls
|
||||
* for the same template name, so we memoize the result for the lifetime of this locator instance.
|
||||
*/
|
||||
private final ConcurrentHashMap<String, Optional<String>> templatePathCache = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link GeneratorTemplateContentLocator} for the provided {@link CodegenConfig}
|
||||
*
|
||||
@@ -51,12 +60,25 @@ public class GeneratorTemplateContentLocator implements TemplatePathLocator {
|
||||
* 4) (embedded template dir)
|
||||
* <p>
|
||||
* Where "template dir" may be user defined and "embedded template dir" are the built-in templates for the given generator.
|
||||
* <p>
|
||||
* Results are cached per {@code relativeTemplateFile} name because the filesystem/classpath probes are expensive
|
||||
* and the outcome is constant for the lifetime of this locator instance.
|
||||
*
|
||||
* @param relativeTemplateFile Template file
|
||||
* @return String Full template file path
|
||||
* @return String Full template file path, or {@code null} if the template does not exist in any location
|
||||
*/
|
||||
@Override
|
||||
public String getFullTemplatePath(String relativeTemplateFile) {
|
||||
return templatePathCache
|
||||
.computeIfAbsent(relativeTemplateFile, key -> Optional.ofNullable(resolveFullTemplatePath(key)))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the actual filesystem/classpath probes to find the full template path.
|
||||
* Called at most once per unique {@code relativeTemplateFile} value; all subsequent lookups use the cache.
|
||||
*/
|
||||
private String resolveFullTemplatePath(String relativeTemplateFile) {
|
||||
CodegenConfig config = this.codegenConfig;
|
||||
|
||||
//check the supplied template library folder for the file
|
||||
|
||||
+45
-12
@@ -38,8 +38,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class HandlebarsEngineAdapter extends AbstractTemplatingEngineAdapter {
|
||||
final Logger LOGGER = LoggerFactory.getLogger(HandlebarsEngineAdapter.class);
|
||||
@@ -48,7 +48,24 @@ public class HandlebarsEngineAdapter extends AbstractTemplatingEngineAdapter {
|
||||
// We use this as a simple lookup for valid file name extensions. This adapter will inspect .mustache (built-in) and infer the relevant handlebars filename
|
||||
private final String[] canCompileFromExtensions = {".handlebars", ".hbs", ".mustache"};
|
||||
private boolean infiniteLoops = false;
|
||||
@Setter private boolean prettyPrint = false;
|
||||
@Setter
|
||||
private boolean prettyPrint = false;
|
||||
|
||||
/**
|
||||
* Per-executor cache of fully-configured {@link Handlebars} engine instances.
|
||||
* Each executor gets its own engine because the engine's {@link TemplateLoader} closes over the
|
||||
* executor; sharing an engine across executors would silently resolve templates from the wrong source.
|
||||
* {@link ConcurrentHashMap#computeIfAbsent} ensures the engine is built at most once per executor.
|
||||
*/
|
||||
private final ConcurrentHashMap<TemplatingExecutor, Handlebars> engineCache = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Per-executor cache of compiled {@link Template} objects.
|
||||
* Keying on the executor instance eliminates the non-atomic check-clear-update invalidation
|
||||
* that the previous single-cache approach required; no state ever needs to be cleared.
|
||||
*/
|
||||
private final ConcurrentHashMap<TemplatingExecutor, ConcurrentHashMap<String, Template>> templateCaches =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Provides an identifier used to load the adapter. This could be a name, uuid, or any other string.
|
||||
@@ -63,13 +80,6 @@ public class HandlebarsEngineAdapter extends AbstractTemplatingEngineAdapter {
|
||||
@Override
|
||||
public String compileTemplate(TemplatingExecutor executor,
|
||||
Map<String, Object> bundle, String templateFile) throws IOException {
|
||||
TemplateLoader loader = new AbstractTemplateLoader() {
|
||||
@Override
|
||||
public TemplateSource sourceAt(String location) {
|
||||
return findTemplate(executor, location);
|
||||
}
|
||||
};
|
||||
|
||||
Context context = Context
|
||||
.newBuilder(bundle)
|
||||
.resolver(
|
||||
@@ -79,9 +89,33 @@ public class HandlebarsEngineAdapter extends AbstractTemplatingEngineAdapter {
|
||||
AccessAwareFieldValueResolver.INSTANCE)
|
||||
.build();
|
||||
|
||||
// Each executor gets its own Handlebars engine (the loader closes over the executor) and its
|
||||
// own compiled-template cache. computeIfAbsent is atomic, so concurrent calls with the same
|
||||
// executor share one engine/cache rather than racing to create duplicates.
|
||||
Handlebars handlebars = engineCache.computeIfAbsent(executor, this::buildHandlebars);
|
||||
ConcurrentHashMap<String, Template> cache =
|
||||
templateCaches.computeIfAbsent(executor, k -> new ConcurrentHashMap<>());
|
||||
|
||||
// Manual get → compile → put so IOException propagates naturally.
|
||||
Template tmpl = cache.get(templateFile);
|
||||
if (tmpl == null) {
|
||||
tmpl = handlebars.compile(templateFile);
|
||||
cache.put(templateFile, tmpl);
|
||||
}
|
||||
return tmpl.apply(context);
|
||||
}
|
||||
|
||||
/** Constructs and fully configures a {@link Handlebars} engine for the given executor. */
|
||||
private Handlebars buildHandlebars(TemplatingExecutor executor) {
|
||||
TemplateLoader loader = new AbstractTemplateLoader() {
|
||||
@Override
|
||||
public TemplateSource sourceAt(String location) {
|
||||
return findTemplate(executor, location);
|
||||
}
|
||||
};
|
||||
Handlebars handlebars = new Handlebars(loader);
|
||||
handlebars.registerHelperMissing((obj, options) -> {
|
||||
LOGGER.warn(String.format(Locale.ROOT, "Unregistered helper name '%s', processing template:%n%s", options.helperName, options.fn.text()));
|
||||
LOGGER.warn("Unregistered helper name '{}', processing template:\n{}", options.helperName, options.fn.text());
|
||||
return "";
|
||||
});
|
||||
handlebars.registerHelper("json", Jackson2Helper.INSTANCE);
|
||||
@@ -90,8 +124,7 @@ public class HandlebarsEngineAdapter extends AbstractTemplatingEngineAdapter {
|
||||
handlebars.registerHelpers(org.openapitools.codegen.templating.handlebars.StringHelpers.class);
|
||||
handlebars.setInfiniteLoops(infiniteLoops);
|
||||
handlebars.setPrettyPrint(prettyPrint);
|
||||
Template tmpl = handlebars.compile(templateFile);
|
||||
return tmpl.apply(context);
|
||||
return handlebars;
|
||||
}
|
||||
|
||||
@SuppressWarnings("java:S108")
|
||||
|
||||
+31
-4
@@ -31,6 +31,7 @@ import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
public class MustacheEngineAdapter implements TemplatingEngineAdapter {
|
||||
@@ -51,6 +52,20 @@ public class MustacheEngineAdapter implements TemplatingEngineAdapter {
|
||||
@Getter @Setter
|
||||
Mustache.Compiler compiler = Mustache.compiler();
|
||||
|
||||
/**
|
||||
* Per-executor cache of template file name → compiled {@link Template}.
|
||||
* <p>
|
||||
* Keying on the executor instance eliminates the non-atomic check-clear-update invalidation pattern
|
||||
* that the previous single-cache approach required. Each executor gets its own independent inner
|
||||
* map, so different executors (e.g. different generator runs, test fixtures) can never observe
|
||||
* each other's compiled templates, and no state ever needs to be cleared.
|
||||
* <p>
|
||||
* {@link ConcurrentHashMap#computeIfAbsent} guarantees that the inner map for a given executor
|
||||
* is created exactly once even under concurrent access.
|
||||
*/
|
||||
private final ConcurrentHashMap<TemplatingExecutor, ConcurrentHashMap<String, Template>> compiledTemplateCaches =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Compiles a template into a string
|
||||
*
|
||||
@@ -62,10 +77,22 @@ public class MustacheEngineAdapter implements TemplatingEngineAdapter {
|
||||
*/
|
||||
@Override
|
||||
public String compileTemplate(TemplatingExecutor executor, Map<String, Object> bundle, String templateFile) throws IOException {
|
||||
Template tmpl = compiler
|
||||
.withLoader(name -> findTemplate(executor, name))
|
||||
.defaultValue("")
|
||||
.compile(executor.getFullTemplateContents(templateFile));
|
||||
// Each executor gets its own compiled-template cache. computeIfAbsent is atomic, so two threads
|
||||
// racing on the same executor key will share one inner map rather than creating two separate ones.
|
||||
ConcurrentHashMap<String, Template> cache =
|
||||
compiledTemplateCaches.computeIfAbsent(executor, k -> new ConcurrentHashMap<>());
|
||||
|
||||
// Manual get → compile → put so IOException propagates naturally.
|
||||
// At worst, two threads compile the same template simultaneously; the last writer wins,
|
||||
// which is harmless because compilation is pure/deterministic.
|
||||
Template tmpl = cache.get(templateFile);
|
||||
if (tmpl == null) {
|
||||
tmpl = compiler
|
||||
.withLoader(name -> findTemplate(executor, name))
|
||||
.defaultValue("")
|
||||
.compile(executor.getFullTemplateContents(templateFile));
|
||||
cache.put(templateFile, tmpl);
|
||||
}
|
||||
StringWriter out = new StringWriter();
|
||||
|
||||
// the value of bundle[MUSTACHE_PARENT_CONTEXT] is used a parent content in mustache.
|
||||
|
||||
Reference in New Issue
Block a user