diff --git a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/GenerateBatch.java b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/GenerateBatch.java index f2823ce366d8..284022d16e3e 100644 --- a/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/GenerateBatch.java +++ b/modules/openapi-generator-cli/src/main/java/org/openapitools/codegen/cmd/GenerateBatch.java @@ -148,6 +148,8 @@ public void execute() { ExecutorService executor = Executors.newFixedThreadPool(numThreads); + long startTime = System.currentTimeMillis(); + // Execute each configurator on a separate pooled thread. configurators.forEach(configurator -> { GenerationRunner runner = new GenerationRunner(configurator, rootDir, Boolean.TRUE.equals(failFast), Boolean.TRUE.equals(clean)); @@ -163,12 +165,18 @@ public void execute() { executor.awaitTermination(awaitFor, TimeUnit.MINUTES); + long elapsedMs = System.currentTimeMillis() - startTime; + long minutes = TimeUnit.MILLISECONDS.toMinutes(elapsedMs); + long seconds = TimeUnit.MILLISECONDS.toSeconds(elapsedMs) % 60; + long millis = elapsedMs % 1000; + String elapsed = String.format(Locale.ROOT, "%dm %ds %dms", minutes, seconds, millis); + int failCount = failures.intValue(); if (failCount > 0) { - System.err.println(String.format(Locale.ROOT, "[FAIL] Completed with %d failures, %d successes", failCount, successes.intValue())); + System.err.println(String.format(Locale.ROOT, "[FAIL] Completed with %d failures, %d successes in %s", failCount, successes.intValue(), elapsed)); System.exit(1); } else { - System.out.println(String.format(Locale.ROOT, "[SUCCESS] Batch generation finished %d generators successfully.", successes.intValue())); + System.out.println(String.format(Locale.ROOT, "[SUCCESS] Batch generation finished %d generators successfully in %s.", successes.intValue(), elapsed)); } } catch (InterruptedException e) { e.printStackTrace(); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index 1ff331cbe6dd..8af0ae7022c7 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -69,6 +69,7 @@ import org.openapitools.codegen.utils.ExamplesUtils; import org.openapitools.codegen.utils.ModelUtils; import org.openapitools.codegen.utils.OneOfImplementorAdditionalData; +import org.openapitools.codegen.utils.PatternCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -79,7 +80,6 @@ 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; @@ -98,8 +98,6 @@ public class DefaultCodegen implements CodegenConfig { private final Logger LOGGER = LoggerFactory.getLogger(DefaultCodegen.class); - public static final Pattern SPLIT_ON_SEMICOLON_OR_NEWLINE_REGEX = Pattern.compile("\\s*(;|\\r?\\n)\\s*"); // Splits on semicolon or new line, ignoring surrounding whitespace - public static FeatureSet DefaultFeatureSet; // A cache of sanitized words. The sanitizeName() method is invoked many times with the same @@ -245,18 +243,107 @@ apiTemplateFiles are for API outputs only (controllers/handlers). // sort operations by default protected boolean skipSortingOperations = false; + // --- MIME type patterns --- 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 REMOVE_CHAR_UNICODE_PATTERN_CACHE = new ConcurrentHashMap<>(); + + // --- HTTP / path patterns --- + protected static final Pattern STARTS_WITH_SLASH = Pattern.compile("^/.*"); + protected static final Pattern UNESCAPED_SLASH = Pattern.compile("(?= 1; } @@ -1172,11 +1259,10 @@ public String escapeText(String input) { // replace " with \" // outer unescape to retain the original multi-byte characters // finally escalate characters avoiding code injection + String unescaped = StringEscapeUtils.unescapeJava( + StringEscapeUtils.escapeJava(input).replace("\\/", "/")); return escapeUnsafeCharacters( - StringEscapeUtils.unescapeJava( - StringEscapeUtils.escapeJava(input) - .replace("\\/", "/")) - .replaceAll("[\\t\\n\\r]", " ") + CONTROL_WHITESPACE.matcher(unescaped).replaceAll(" ") .replace("\\", "\\\\") .replace("\"", "\\\"")); } @@ -1216,7 +1302,7 @@ public String escapeTextWhileAllowingNewLines(String input) { StringEscapeUtils.unescapeJava( StringEscapeUtils.escapeJava(input) .replace("\\/", "/")) - .replaceAll("\\t", " ") + .replace("\t", " ") .replace("\\", "\\\\") .replace("\"", "\\\"")); } @@ -4360,7 +4446,7 @@ void updateDefaultToEmptyContainer(CodegenProperty cp, Schema p) { * @param input a set of rules separated by `|` */ void parseDefaultToEmptyContainer(String input) { - String[] inputs = input.split("[|]"); + String[] inputs = SPLIT_ON_PIPE_CHAR.split(input); String containerType; for (String rule : inputs) { if (StringUtils.isEmpty(rule)) { @@ -5261,7 +5347,7 @@ public CodegenCallback fromCallback(String name, Callback callback, List } else { boolean genId = op.getOperationId() == null; if (genId) { - op.setOperationId(getOrGenerateOperationId(op, c.name + "_" + expression.replaceAll("\\{\\$.*}", ""), method)); + op.setOperationId(getOrGenerateOperationId(op, c.name + "_" + CALLBACK_EXPRESSION_PARAM.matcher(expression).replaceAll(""), method)); } if (op.getExtensions() == null) { @@ -5833,8 +5919,8 @@ protected String getOrGenerateOperationId(Operation operation, String path, Stri if (StringUtils.isBlank(operationId)) { String tmpPath = path; - tmpPath = tmpPath.replaceAll("\\{", ""); - tmpPath = tmpPath.replaceAll("\\}", ""); + tmpPath = tmpPath.replace("{", ""); + tmpPath = tmpPath.replace("}", ""); String[] parts = (tmpPath + "/" + httpMethod).split("/"); StringBuilder builder = new StringBuilder(); if ("/".equals(tmpPath)) { @@ -5862,7 +5948,7 @@ protected String getOrGenerateOperationId(Operation operation, String path, Stri // remove prefix in operationId if (removeOperationIdPrefix) { // The prefix is everything before the removeOperationIdPrefixCount occurrence of removeOperationIdPrefixDelimiter - String[] components = operationId.split("[" + removeOperationIdPrefixDelimiter + "]"); + String[] components = PatternCache.get("[" + removeOperationIdPrefixDelimiter + "]").split(operationId); if (components.length > 1) { // If removeOperationIdPrefixCount is -1 or bigger that the number of occurrences, uses the last one int component_number = removeOperationIdPrefixCount == -1 ? components.length - 1 : removeOperationIdPrefixCount; @@ -6757,15 +6843,13 @@ public String sanitizeName(final String name, String removeCharRegEx, ArrayList< // /api/films/get => _api_films_get // \api\films\get => _api_films_get - modifiable = modifiable.replaceAll("/", "_"); - modifiable = modifiable.replaceAll("\\\\", "_"); + modifiable = modifiable.replace("/", "_"); + modifiable = modifiable.replace("\\", "_"); // remove everything else other than word, number and _ // $php_variable => php_variable if (allowUnicodeIdentifiers) { //could be converted to a single line with ?: operator - modifiable = REMOVE_CHAR_UNICODE_PATTERN_CACHE - .computeIfAbsent(sanitizeNameOptions.getRemoveCharRegEx(), - regex -> Pattern.compile(regex, Pattern.UNICODE_CHARACTER_CLASS)) + modifiable = PatternCache.get(sanitizeNameOptions.getRemoveCharRegEx(), Pattern.UNICODE_CHARACTER_CLASS) .matcher(modifiable).replaceAll(""); } else { modifiable = modifiable.replaceAll(sanitizeNameOptions.getRemoveCharRegEx(), ""); @@ -6792,7 +6876,7 @@ public String sanitizeTag(String tag) { tag = camelize(sanitizeName(tag)); // tag starts with numbers - if (tag.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(tag).matches()) { tag = "Class" + tag; } @@ -7053,8 +7137,8 @@ public String addRegularExpressionDelimiter(String pattern) { return pattern; } - if (!pattern.matches("^/.*")) { - return "/" + pattern.replaceAll("/", "\\\\/") + "/"; + if (!pattern.startsWith("/")) { + return "/" + pattern.replace("/", "\\/") + "/"; } return pattern; @@ -7704,7 +7788,7 @@ protected void addBodyModelSchema(CodegenParameter codegenParameter, String name if (codegenProperty != null && codegenProperty.getComplexType() != null && codegenProperty.getComplexType().contains(" | ")) { // TODO move this splitting logic to the generator that needs it only - List parts = Arrays.asList(codegenProperty.getComplexType().split(" \\| ")); + List parts = Arrays.asList(SPLIT_ON_PIPE.split(codegenProperty.getComplexType())); imports.addAll(parts); String codegenModelName = codegenProperty.getComplexType(); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java index 0fa4b0dceea5..588bb27523df 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java @@ -1435,7 +1435,7 @@ protected File processTemplateToFile(Map templateData, String te private final Set seenFilesLower = new HashSet<>(); private File processTemplateToFile(Map templateData, String templateName, String outputFilename, boolean shouldGenerate, String skippedByOption, String intendedOutputDir) throws IOException { - String adjustedOutputFilename = outputFilename.replaceAll("//", "/").replace('/', File.separatorChar); + String adjustedOutputFilename = outputFilename.replace("//", "/").replace('/', File.separatorChar); File target = new File(adjustedOutputFilename); if (ignoreProcessor.allowsFile(target)) { if (shouldGenerate) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/InlineModelResolver.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/InlineModelResolver.java index 777fed9b2cb4..e9ee7b64de0d 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/InlineModelResolver.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/InlineModelResolver.java @@ -37,8 +37,11 @@ import org.slf4j.LoggerFactory; import java.util.*; +import java.util.regex.Pattern; public class InlineModelResolver { + private static final Pattern LEADING_DIGIT = Pattern.compile("^[0-9]"); + private static final Pattern NON_ALPHANUMERIC = Pattern.compile("[^A-Za-z0-9]"); private OpenAPI openAPI; private Map addedModels = new HashMap<>(); private Map generatedSignature = new HashMap<>(); @@ -785,9 +788,9 @@ private void addGenerated(String name, Schema model) { * @param name name to be processed to make sure it's sanitized */ private String sanitizeName(final String name) { - return name - .replaceAll("^[0-9]", "_$0") // e.g. 12object => _12object - .replaceAll("[^A-Za-z0-9]", "_"); // e.g. io.schema.User name => io_schema_User_name + return NON_ALPHANUMERIC.matcher( + LEADING_DIGIT.matcher(name).replaceFirst("_$0")) // e.g. 12object => _12object + .replaceAll("_"); // e.g. io.schema.User name => io_schema_User_name } /** diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractAdaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractAdaCodegen.java index 4762205e7e1b..0b196dd03377 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractAdaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractAdaCodegen.java @@ -38,6 +38,7 @@ import java.io.File; import java.util.*; +import java.util.regex.Pattern; import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER; import static org.openapitools.codegen.utils.StringUtils.camelize; @@ -398,7 +399,7 @@ public String toModelName(final String name) { } // model name starts with number - if (result.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(result).matches()) { String modelName = "Model_" + result; // e.g. 200Response => Model_200Response (after camelize) LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, modelName); @@ -439,16 +440,16 @@ else if (getSymbolName(value) != null) { else if ("Integer".equals(datatype) || "Long".equals(datatype) || "Float".equals(datatype) || "Double".equals(datatype)) { String varName = "NUMBER_" + value; - varName = varName.replaceAll("-", "MINUS_"); - varName = varName.replaceAll("\\+", "PLUS_"); - varName = varName.replaceAll("\\.", "_DOT_"); + varName = varName.replace("-", "MINUS_"); + varName = varName.replace("+", "PLUS_"); + varName = varName.replace(".", "_DOT_"); var = varName; } // string else { - var = value.replaceAll("\\W+", "_").toUpperCase(Locale.ROOT); - if (var.matches("\\d.*")) { + var = NON_WORD_PLUS.matcher(value).replaceAll("_").toUpperCase(Locale.ROOT); + if (STARTS_WITH_DIGIT.matcher(var).matches()) { var = "_" + var; } else { var = sanitizeName(var); @@ -1094,7 +1095,7 @@ private List postProcessAuthMethod(List authMe ident = name; } scopeIndex++; - ident = toAdaIdentifier(sanitizeName(ident.replaceAll(":", "_")), "S_"); + ident = toAdaIdentifier(sanitizeName(ident.replace(":", "_")), "S_"); if (operationsScopes.containsValue(ident)) { ident = ident + "_" + scopeIndex; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractApexCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractApexCodegen.java index 6a090df03d5a..ad0f2675e59b 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractApexCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractApexCodegen.java @@ -32,6 +32,7 @@ import org.slf4j.LoggerFactory; import java.util.*; +import java.util.regex.Pattern; import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER; import static org.openapitools.codegen.utils.StringUtils.camelize; @@ -39,7 +40,17 @@ public abstract class AbstractApexCodegen extends DefaultCodegen implements CodegenConfig { private final Logger LOGGER = LoggerFactory.getLogger(AbstractApexCodegen.class); - @Setter protected Boolean serializableModel = false; + private static final Pattern DATE_FORMAT = Pattern.compile("^\\d{4}(-\\d{2}){2}"); + private static final Pattern DATETIME_FORMAT = Pattern.compile("^\\d{4}([-T:]\\d{2}){5}.+"); + private static final Pattern NON_NUMERIC = Pattern.compile("[^-0-9.]"); + private static final Pattern UNSAFE_PACKAGE_CHARS = Pattern.compile("[^a-zA-Z0-9_.]"); + private static final Pattern INTEGER_PATTERN = Pattern.compile("^-?\\d+$"); + private static final Pattern UNDERSCORE_CLASS = Pattern.compile("^_*class$"); + private static final Pattern DATE_SEP_ZERO = Pattern.compile("-0?"); + private static final Pattern DATETIME_SEP_ZERO = Pattern.compile("[-T:]0?"); + + @Setter + protected Boolean serializableModel = false; public AbstractApexCodegen() { super(); @@ -77,11 +88,9 @@ public String escapeReservedWord(String name) { public String sanitizeName(String name) { name = super.sanitizeName(name); if (name.contains("__")) { // Preventing namespacing - name = name.replaceAll("__", "_"); - } - if (name.matches("^\\d.*")) { // Prevent named credentials with leading number - name = name.replaceAll("^\\d.*", ""); + name = name.replace("__", "_"); } + name = STARTS_WITH_DIGIT.matcher(name).replaceAll(""); // Prevent named credentials with leading number return name; } @@ -90,7 +99,7 @@ public String toVarName(String name) { // sanitize name name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. - if (name.toLowerCase(Locale.ROOT).matches("^_*class$")) { + if (UNDERSCORE_CLASS.matcher(name.toLowerCase(Locale.ROOT)).matches()) { return "propertyClass"; } @@ -99,7 +108,7 @@ public String toVarName(String name) { } // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { if (isReservedWord(name)) { name = escapeReservedWord(name); } @@ -115,7 +124,7 @@ public String toVarName(String name) { name = camelize(name, LOWERCASE_FIRST_LETTER); // for reserved word or word starting with number, append _ - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } @@ -169,7 +178,7 @@ public String toModelName(final String name) { } // model name starts with number - if (camelizedName.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(camelizedName).matches()) { final String modelName = "Model" + camelizedName; // e.g. 200Response => Model200Response (after camelize) LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, modelName); @@ -318,8 +327,8 @@ public String toExampleValue(Schema p) { p.setExample(example); example = "EncodingUtil.base64Decode('" + example + "')"; } else if (ModelUtils.isDateSchema(p)) { - if (example.matches("^\\d{4}(-\\d{2}){2}")) { - example = example.substring(0, 10).replaceAll("-0?", ", "); + if (DATE_FORMAT.matcher(example).find()) { + example = DATE_SEP_ZERO.matcher(example.substring(0, 10)).replaceAll(", "); } else if (example.isEmpty()) { example = "2000, 1, 23"; } else { @@ -329,8 +338,8 @@ public String toExampleValue(Schema p) { } example = "Date.newInstance(" + example + ")"; } else if (ModelUtils.isDateTimeSchema(p)) { - if (example.matches("^\\d{4}([-T:]\\d{2}){5}.+")) { - example = example.substring(0, 19).replaceAll("[-T:]0?", ", "); + if (DATETIME_FORMAT.matcher(example).find()) { + example = DATETIME_SEP_ZERO.matcher(example.substring(0, 19)).replaceAll(", "); } else if (example.isEmpty()) { example = "2000, 1, 23, 4, 56, 7"; } else { @@ -340,7 +349,7 @@ public String toExampleValue(Schema p) { } example = "Datetime.newInstanceGmt(" + example + ")"; } else if (ModelUtils.isNumberSchema(p)) { - example = example.replaceAll("[^-0-9.]", ""); + example = NON_NUMERIC.matcher(example).replaceAll(""); example = example.isEmpty() ? "1.3579" : example; } else if (ModelUtils.isFileSchema(p)) { if (example.isEmpty()) { @@ -380,7 +389,7 @@ public String toExampleValue(Schema p) { ? "'046b6c7f-0b8a-43b9-b35d-6489e6daee91'" : "'" + escapeText(example) + "'"; } else if (ModelUtils.isIntegerSchema(p)) { - example = example.matches("^-?\\d+$") ? example : "0"; + example = INTEGER_PATTERN.matcher(example).matches() ? example : "0"; } else if (ModelUtils.isObjectSchema(p)) { example = example.isEmpty() ? "null" : example; } else { @@ -524,15 +533,15 @@ public String toEnumVarName(String value, String datatype) { if ("Integer".equals(datatype) || "Long".equals(datatype) || "Float".equals(datatype) || "Double".equals(datatype)) { String varName = "NUMBER_" + value; - varName = varName.replaceAll("-", "MINUS_"); - varName = varName.replaceAll("\\+", "PLUS_"); - varName = varName.replaceAll("\\.", "_DOT_"); + varName = varName.replace("-", "MINUS_"); + varName = varName.replace("+", "PLUS_"); + varName = varName.replace(".", "_DOT_"); return varName; } // string - String var = value.replaceAll("\\W+", "_").toUpperCase(Locale.ROOT); - if (var.matches("\\d.*")) { + String var = NON_WORD_PLUS.matcher(value).replaceAll("_").toUpperCase(Locale.ROOT); + if (STARTS_WITH_DIGIT.matcher(var).matches()) { return "_" + var; } else { return var; @@ -618,7 +627,7 @@ private static CodegenModel reconcileInlineEnums(CodegenModel codegenModel, Code private static String sanitizePackageName(String packageName) { packageName = packageName.trim(); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. - packageName = packageName.replaceAll("[^a-zA-Z0-9_\\.]", "_"); + packageName = UNSAFE_PACKAGE_CHARS.matcher(packageName).replaceAll("_"); if (Strings.isNullOrEmpty(packageName)) { return "invalidPackageName"; } @@ -628,7 +637,7 @@ private static String sanitizePackageName(String packageName) { private String sanitizePath(String p) { //prefer replace a ", instead of a fuLL URL encode for readability - return p.replaceAll("\"", "%22"); + return p.replace("\"", "%22"); } @Override diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractCSharpCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractCSharpCodegen.java index ec07161ff199..7ab80bf0deae 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractCSharpCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractCSharpCodegen.java @@ -54,6 +54,10 @@ public abstract class AbstractCSharpCodegen extends DefaultCodegen { + /** Matches a regex literal that carries modifier flags after the closing slash, e.g. {@code /foo/i}. */ + private static final Pattern HAS_MODIFIERS = Pattern.compile(".*/[gmiyuvsdlnx]+$"); + private static final Pattern UNESCAPED_DOUBLE_QUOTE = Pattern.compile("(? created_at - name = name.replaceAll("-", "_"); + name = name.replace("-", "_"); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } @@ -1495,7 +1499,7 @@ public String escapeReservedWord(CodegenModel model, String name) { @Override public String escapeReservedWord(String name) { if (isReservedWord(name) || - name.matches("^\\d.*")) { + STARTS_WITH_DIGIT.matcher(name).find()) { name = AbstractCSharpCodegen.invalidParameterNamePrefix + camelize(name); } return name; @@ -1673,7 +1677,7 @@ public String toModelName(String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).find()) { LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, camelize("model_" + name)); name = camelize("model_" + name); // e.g. 200Response => Model200Response (after camelize) @@ -1768,11 +1772,11 @@ public String toEnumValue(String value, String datatype) { return value; } - final String partiallyEscaped = value + final String partiallyEscaped = UNESCAPED_DOUBLE_QUOTE.matcher(value .replace("\n", "\\n") .replace("\t", "\\t") - .replace("\r", "\\r") - .replaceAll("(? vendorExtensi // gmiyuvsd - ecma modifiers // l - legacy modifier provided by this library, provides a way to opt out of culture invariant // nx - c# modifiers https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-options - Pattern hasModifiers = Pattern.compile(".*/[gmiyuvsdlnx]+$"); - - int end = hasModifiers.matcher(pattern).find() + int end = HAS_MODIFIERS.matcher(pattern).find() ? pattern.lastIndexOf('/') : pattern.length() - 1; @@ -2091,7 +2093,7 @@ public String addRegularExpressionDelimiter(String pattern) { return pattern; } - if (!pattern.matches("^/.*")) { + if (!pattern.startsWith("/")) { return "/" + pattern + "/"; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractCppCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractCppCodegen.java index 8950c1d4b761..8263c755da69 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractCppCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractCppCodegen.java @@ -42,6 +42,7 @@ abstract public class AbstractCppCodegen extends DefaultCodegen implements CodegenConfig { private final Logger LOGGER = LoggerFactory.getLogger(AbstractCppCodegen.class); + protected static final String RESERVED_WORD_PREFIX_OPTION = "reservedWordPrefix"; protected static final String RESERVED_WORD_PREFIX_DESC = "Prefix to prepend to reserved words in order to avoid conflicts"; protected String reservedWordPrefix = "r_"; @@ -159,7 +160,7 @@ public AbstractCppCodegen() { @SuppressWarnings("static-method") public String sanitizeName(String name) { String sanitizedName = super.sanitizeName(name); - sanitizedName = sanitizedName.replaceAll("-", ""); + sanitizedName = sanitizedName.replace("-", ""); return sanitizedName; } @@ -220,7 +221,7 @@ public String toVarName(String name) { return sanitizeName(name); } - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { return escapeReservedWord(name); } @@ -262,7 +263,7 @@ public String toParamName(String name) { return parameterNameMapping.get(name); } - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { return escapeReservedWord(name); } @@ -279,7 +280,7 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required) { } else { nameInCamelCase = sanitizeName(nameInCamelCase); } - if (isReservedWord(nameInCamelCase) || nameInCamelCase.matches("^\\d.*")) { + if (isReservedWord(nameInCamelCase) || STARTS_WITH_DIGIT.matcher(nameInCamelCase).matches()) { nameInCamelCase = escapeReservedWord(nameInCamelCase); } property.nameInCamelCase = nameInCamelCase; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractDartCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractDartCodegen.java index 4d7eb4dbe7c1..fd603b126f07 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractDartCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractDartCodegen.java @@ -26,6 +26,7 @@ import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.*; +import java.util.regex.Pattern; import java.util.stream.Stream; import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER; @@ -35,6 +36,8 @@ public abstract class AbstractDartCodegen extends DefaultCodegen { private final Logger LOGGER = LoggerFactory.getLogger(AbstractDartCodegen.class); + private static final Pattern STARTS_WITH_OPT_NEG_DIGIT = Pattern.compile("^-?\\d.*"); + protected static final List DEFAULT_SUPPORTED_CONTENT_TYPES = Arrays.asList( "application/json", "application/x-www-form-urlencoded", "multipart/form-data"); @@ -413,10 +416,10 @@ public String toVarName(String name) { if (name.equals("_")) { return "underscore"; } - name = name.replaceAll("^_", ""); + name = FIRST_LEADING_UNDERSCORE.matcher(name).replaceAll(""); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } @@ -432,7 +435,7 @@ public String toVarName(String name) { // pet_id => petId name = camelize(name, LOWERCASE_FIRST_LETTER); - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { name = "n" + name; } @@ -496,7 +499,7 @@ public String toModelName(final String name) { } // model name starts with number - if (camelizedName.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(camelizedName).matches()) { final String modelName = "Model" + camelizedName; // e.g. 200Response => Model200Response (after camelize) LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, modelName); return modelName; @@ -874,7 +877,7 @@ public String toEnumVarName(String value, String datatype) { if (("number".equalsIgnoreCase(datatype) || "double".equalsIgnoreCase(datatype) || "int".equalsIgnoreCase(datatype)) && - value.matches("^-?\\d.*")) { + STARTS_WITH_OPT_NEG_DIGIT.matcher(value).matches()) { // Only rename numeric values when the datatype is numeric // AND the name is not changed by enum extensions (matches a numeric value). boolean isNegative = value.startsWith("-"); @@ -907,7 +910,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { String newOperationId = camelize("call_" + operationId, LOWERCASE_FIRST_LETTER); LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, newOperationId); operationId = newOperationId; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractEiffelCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractEiffelCodegen.java index 83f3fac174f5..a68977d1e297 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractEiffelCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractEiffelCodegen.java @@ -32,6 +32,7 @@ import org.slf4j.LoggerFactory; import java.util.*; +import java.util.regex.Pattern; import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER; import static org.openapitools.codegen.utils.StringUtils.camelize; @@ -40,6 +41,8 @@ public abstract class AbstractEiffelCodegen extends DefaultCodegen implements CodegenConfig { private final Logger LOGGER = LoggerFactory.getLogger(AbstractEiffelCodegen.class); + private static final Pattern UNCAMELIZE_UPPER = Pattern.compile("(.)(\\p{Upper})"); + private final Set parentModels = new HashSet<>(); private final Multimap childrenByParent = ArrayListMultimap.create(); @@ -126,7 +129,7 @@ public String escapeReservedWord(String name) { if (this.reservedWordsMappings().containsKey(name)) { return this.reservedWordsMappings().get(name); } - if (name.matches("^\\d.*")) {// prepend var_ + if (STARTS_WITH_DIGIT.matcher(name).matches()) {// prepend var_ return "var_" + name; } return "var_" + name; @@ -135,10 +138,10 @@ public String escapeReservedWord(String name) { @Override public String toVarName(String name) { // replace - with _ e.g. created-at => created_at - name = sanitizeName(name.replaceAll("-", "_")); + name = sanitizeName(name.replace("-", "_")); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } @@ -156,7 +159,7 @@ public String toVarName(String name) { } // for reserved word or word starting with number, append - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } @@ -201,7 +204,7 @@ public String toModelFilename(String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, "model_" + name); name = "model_" + name; // e.g. 200Response => Model200Response @@ -221,7 +224,7 @@ public String toModelFilename(String name) { @Override public String toApiFilename(String name) { // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); // FIXME: a parameter should not be + name = name.replace("-", "_"); // FIXME: a parameter should not be // assigned. Also declare the // methods parameters as 'final'. @@ -348,7 +351,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { LOGGER.warn(operationId + " (starting with a number) cannot be used as method sname. Renamed to " + camelize("call_" + operationId), true); sanitizedOperationId = camelize("call_" + sanitizedOperationId, LOWERCASE_FIRST_LETTER); } @@ -584,7 +587,7 @@ public String toInstantiationType(Schema p) { } public String unCamelize(String name) { - return name.replaceAll("(.)(\\p{Upper})", "$1_$2").toLowerCase(Locale.ROOT); + return UNCAMELIZE_UPPER.matcher(name).replaceAll("$1_$2").toLowerCase(Locale.ROOT); } public String toEiffelFeatureStyle(String operationId) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractFSharpCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractFSharpCodegen.java index 7ca5e99015c2..edfd064108a3 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractFSharpCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractFSharpCodegen.java @@ -44,6 +44,9 @@ public abstract class AbstractFSharpCodegen extends DefaultCodegen implements CodegenConfig { + // ...existing code... + + protected boolean optionalAssemblyInfoFlag = true; protected boolean optionalProjectFileFlag = true; @@ -627,7 +630,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, camelize(sanitizeName("call_" + operationId))); operationId = "call_" + operationId; } @@ -670,14 +673,14 @@ public String toVarName(String name) { name = sanitizeName(name); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } name = getNameUsingModelPropertyNaming(name); // for reserved word or word starting with number, append _ - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } return name; @@ -689,10 +692,10 @@ public String toParamName(String name) { name = sanitizeName(name); // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); + name = name.replace("-", "_"); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } @@ -701,7 +704,7 @@ public String toParamName(String name) { name = camelize(name, LOWERCASE_FIRST_LETTER); // for reserved word or word starting with number, append _ - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } @@ -895,7 +898,7 @@ public String toModelName(String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, camelize("model_" + name)); name = "model_" + name; // e.g. 200Response => Model200Response (after camelize) @@ -959,12 +962,12 @@ public String toEnumVarName(String name, String datatype) { String enumName = sanitizeName(name); - enumName = enumName.replaceFirst("^_", ""); - enumName = enumName.replaceFirst("_$", ""); + enumName = FIRST_LEADING_UNDERSCORE.matcher(enumName).replaceFirst(""); + enumName = LAST_TRAILING_UNDERSCORE.matcher(enumName).replaceFirst(""); enumName = camelize(enumName) + "Enum"; - if (enumName.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(enumName).matches()) { // starts with number return "_" + enumName; } else { return enumName; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGoCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGoCodegen.java index 16b909eab448..bbbdaae36c8b 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGoCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGoCodegen.java @@ -29,6 +29,7 @@ import java.io.File; import java.util.*; +import java.util.regex.Pattern; import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER; import static org.openapitools.codegen.utils.StringUtils.camelize; @@ -39,6 +40,8 @@ public abstract class AbstractGoCodegen extends DefaultCodegen implements Codege private final Logger LOGGER = LoggerFactory.getLogger(AbstractGoCodegen.class); private static final String NUMERIC_ENUM_PREFIX = "_"; + private static final Pattern LEADING_TRAILING_SLASH = Pattern.compile("^/|/$"); + @Setter protected boolean withGoCodegenComment = false; @Setter @@ -213,7 +216,7 @@ public String toVarName(String name) { name = sanitizeName(name); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) return name; // camelize (lower first character) the variable name @@ -227,7 +230,7 @@ public String toVarName(String name) { } // for reserved word or word starting with number, append _ - if (name.matches("^\\d.*")) + if (STARTS_WITH_DIGIT.matcher(name).matches()) name = "Var" + name; if ("AdditionalProperties".equals(name)) { @@ -332,7 +335,7 @@ public String toModel(String name, boolean doUnderscore) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, "model_" + name); name = "model_" + name; // e.g. 200Response => Model200Response (after camelize) @@ -348,7 +351,7 @@ public String toModel(String name, boolean doUnderscore) { public String toApiFilename(String name) { final String apiName; // replace - with _ e.g. created-at => created_at - String api = name.replaceAll("-", "_"); + String api = name.replace("-", "_"); // e.g. PetApi.go => pet_api.go api = "api_" + underscore(api); if (isReservedFilename(api)) { @@ -498,7 +501,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (sanitizedOperationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(sanitizedOperationId).matches()) { LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, camelize("call_" + sanitizedOperationId)); sanitizedOperationId = "call_" + sanitizedOperationId; } @@ -789,7 +792,7 @@ public ModelsMap postProcessModels(ModelsMap objs) { if (cp.pattern != null) { cp.vendorExtensions.put("x-go-custom-tag", "validate:\"regexp=" + - cp.pattern.replace("\\", "\\\\").replaceAll("^/|/$", "") + + LEADING_TRAILING_SLASH.matcher(cp.pattern.replace("\\", "\\\\")).replaceAll("") + "\""); } @@ -931,9 +934,9 @@ public String toEnumVarName(String name, String datatype) { // number if (isNumberType(datatype)) { String varName = name; - varName = varName.replaceAll("-", "MINUS_"); - varName = varName.replaceAll("\\+", "PLUS_"); - varName = varName.replaceAll("\\.", "_DOT_"); + varName = varName.replace("-", "MINUS_"); + varName = varName.replace("+", "PLUS_"); + varName = varName.replace(".", "_DOT_"); return NUMERIC_ENUM_PREFIX + varName; } @@ -944,12 +947,12 @@ public String toEnumVarName(String name, String datatype) { // string String enumName = sanitizeName(underscore(name).toUpperCase(Locale.ROOT)); - enumName = enumName.replaceFirst("^_", ""); - enumName = enumName.replaceFirst("_$", ""); + enumName = FIRST_LEADING_UNDERSCORE.matcher(enumName).replaceFirst(""); + enumName = LAST_TRAILING_UNDERSCORE.matcher(enumName).replaceFirst(""); if (isReservedWord(enumName)) { // reserved word return escapeReservedWord(enumName); - } else if (enumName.matches("\\d.*")) { // starts with a number + } else if (STARTS_WITH_DIGIT.matcher(enumName).matches()) { // starts with a number return NUMERIC_ENUM_PREFIX + enumName; } else { return enumName; @@ -967,7 +970,7 @@ public String toEnumName(CodegenProperty property) { // remove [] for array or map of enum enumName = enumName.replace("[]", ""); - if (enumName.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(enumName).matches()) { // starts with number return NUMERIC_ENUM_PREFIX + enumName; } else { return enumName; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGraphQLCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGraphQLCodegen.java index 0d383f375a61..261554a1b278 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGraphQLCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractGraphQLCodegen.java @@ -38,6 +38,7 @@ public abstract class AbstractGraphQLCodegen extends DefaultCodegen implements C private final Logger LOGGER = LoggerFactory.getLogger(AbstractGraphQLCodegen.class); + protected String specFolder = "spec"; @Setter protected String packageName = "openapi2graphql"; @Setter protected String packageVersion = "1.0.0"; @@ -147,10 +148,10 @@ public String modelFileFolder() { @Override public String toVarName(String name) { // replace - with _ e.g. created-at => created_at - name = sanitizeName(name.replaceAll("-", "_")); + name = sanitizeName(name.replace("-", "_")); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) return name; name = camelize(name, LOWERCASE_FIRST_LETTER); @@ -160,7 +161,7 @@ public String toVarName(String name) { name = escapeReservedWord(name); // for reserved word or word starting with number, append _ - if (name.matches("^\\d.*")) + if (STARTS_WITH_DIGIT.matcher(name).matches()) name = camelize("var_" + name); return name; @@ -195,7 +196,7 @@ public String toModelFilename(String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, "model_" + name); name = "model_" + name; // e.g. 200Response => Model200Response (after camelize) @@ -207,7 +208,7 @@ public String toModelFilename(String name) { @Override public String toApiFilename(String name) { // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + name = name.replace("-", "_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. // e.g. PetApi.graphql => pet_api.graphql return underscore(name) + "_api"; @@ -371,9 +372,9 @@ public String toEnumVarName(String name, String datatype) { // number if ("int".equals(datatype) || "double".equals(datatype) || "float".equals(datatype)) { String varName = name; - varName = varName.replaceAll("-", "MINUS_"); - varName = varName.replaceAll("\\+", "PLUS_"); - varName = varName.replaceAll("\\.", "_DOT_"); + varName = varName.replace("-", "MINUS_"); + varName = varName.replace("+", "PLUS_"); + varName = varName.replace(".", "_DOT_"); return varName; } @@ -384,10 +385,10 @@ public String toEnumVarName(String name, String datatype) { // string String enumName = sanitizeName(underscore(name).toUpperCase(Locale.ROOT)); - enumName = enumName.replaceFirst("^_", ""); - enumName = enumName.replaceFirst("_$", ""); + enumName = FIRST_LEADING_UNDERSCORE.matcher(enumName).replaceFirst(""); + enumName = LAST_TRAILING_UNDERSCORE.matcher(enumName).replaceFirst(""); - if (isReservedWord(enumName) || enumName.matches("\\d.*")) { // reserved word or starts with number + if (isReservedWord(enumName) || STARTS_WITH_DIGIT.matcher(enumName).matches()) { // reserved word or starts with number return escapeReservedWord(enumName); } else { return enumName; @@ -401,7 +402,7 @@ public String toEnumName(CodegenProperty property) { // remove [] for array or map of enum enumName = enumName.replace("[]", ""); - if (enumName.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(enumName).matches()) { // starts with number return "_" + enumName; } else { return enumName; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java index 58f7d0e1f226..f02e54d3ba51 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java @@ -83,6 +83,12 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code private final Logger LOGGER = LoggerFactory.getLogger(AbstractJavaCodegen.class); private static final String ARTIFACT_VERSION_DEFAULT_VALUE = "1.0.0"; private static final ZoneId UTC = ZoneId.of("UTC"); + private static final Pattern LOMBOK_ANNOTATION = Pattern.compile("@lombok.(\\w+\\.)*(?\\w+)(\\(.*?\\))?"); + private static final Pattern JAVA_UTIL_IMPORT = Pattern.compile("java\\.util\\.(List|ArrayList|Map|HashMap)"); + private static final Pattern STARTS_WITH_UNDERSCORE_CLASS = Pattern.compile("^_*class$"); + private static final Pattern ANNOTATION_IN_TYPE = Pattern.compile("(?:(?i)@[a-z0-9]*+([(].*[)]|\\s*))*+"); + private static final Pattern NON_ALPHANUMERIC_UNICODE = Pattern.compile("\\P{Alnum}"); + private static final Pattern INVALID_PACKAGE_CHARS = Pattern.compile("[^a-zA-Z0-9_.]"); public static final String DEFAULT_LIBRARY = ""; public static final String DATE_LIBRARY = "dateLibrary"; @@ -452,10 +458,10 @@ public void processOpts() { convertPropertyToStringAndWriteBack(BOOLEAN_GETTER_PREFIX, this::setBooleanGetterPrefix); convertPropertyToBooleanAndWriteBack(IGNORE_ANYOF_IN_ENUM, this::setIgnoreAnyOfInEnum); convertPropertyToTypeAndWriteBack(ADDITIONAL_MODEL_TYPE_ANNOTATIONS, - annotations -> Arrays.asList(annotations.trim().split("\\s*(;|\\r?\\n)\\s*")), + annotations -> Arrays.asList(SPLIT_ON_SEMICOLON_OR_NEWLINE_REGEX.split(annotations.trim())), this::setAdditionalModelTypeAnnotations); convertPropertyToTypeAndWriteBack(ADDITIONAL_ONE_OF_TYPE_ANNOTATIONS, - annotations -> Arrays.asList(annotations.trim().split("\\s*(;|\\r?\\n)\\s*")), + annotations -> Arrays.asList(SPLIT_ON_SEMICOLON_OR_NEWLINE_REGEX.split(annotations.trim())), this::setAdditionalOneOfTypeAnnotations); convertPropertyToTypeAndWriteBack(ADDITIONAL_ENUM_TYPE_ANNOTATIONS, annotations -> Arrays.asList(annotations.split(";")), @@ -920,7 +926,7 @@ public String toVarName(String name) { // sanitize name name = sanitizeName(name, "\\W-[\\$]"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. - if (name.toLowerCase(Locale.ROOT).matches("^_*class$")) { + if (STARTS_WITH_UNDERSCORE_CLASS.matcher(name.toLowerCase(Locale.ROOT)).matches()) { return "propertyClass"; } @@ -929,12 +935,12 @@ public String toVarName(String name) { } // numbers are not allowed at the beginning - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { name = "_" + name; } // if it's all upper case, do nothing - if (name.matches("^[A-Z0-9_]*$")) { + if (ALL_UPPER_UNDERSCORE_DIGITS.matcher(name).matches()) { return name; } @@ -959,7 +965,7 @@ public String toVarName(String name) { } // for reserved word or word starting with number, append _ - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } @@ -1035,7 +1041,7 @@ public String toModelName(final String name) { } // model name starts with number - if (camelizedName.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(camelizedName).matches()) { final String modelName = "Model" + camelizedName; // e.g. 200Response => Model200Response (after camelize) schemaKeyToModelNameCache.put(origName, modelName); LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, @@ -1209,10 +1215,10 @@ private String getStringBeanValidation(Schema items) { if (StringUtils.isNotEmpty(items.getPattern())) { final String pattern = escapeUnsafeCharacters( + CONTROL_WHITESPACE.matcher( StringEscapeUtils.unescapeJava( StringEscapeUtils.escapeJava(items.getPattern()) - .replace("\\/", "/")) - .replaceAll("[\\t\\n\\r]", " ") + .replace("\\/", "/"))).replaceAll(" ") .replace("\\", "\\\\") .replace("\"", "\\\"")); @@ -1832,7 +1838,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { LOGGER.warn(operationId + " (starting with a number) cannot be used as method name. Renamed to " + camelize("call_" + operationId), true); operationId = camelize("call_" + operationId, LOWERCASE_FIRST_LETTER); } @@ -1983,7 +1989,7 @@ public void postProcessResponseWithProperty(CodegenResponse response, CodegenPro */ public String removeAnnotations(String dataType) { if (dataType != null && dataType.contains("@")) { - return dataType.replaceAll("(?:(?i)@[a-z0-9]*+([(].*[)]|\\s*))*+", ""); + return ANNOTATION_IN_TYPE.matcher(dataType).replaceAll(""); } return dataType; } @@ -1997,8 +2003,8 @@ public String removeAnnotations(String dataType) { */ public String sanitizeDataType(String dataType) { String content = removeAnnotations(dataType); - if (content != null && content.matches(".*\\P{Alnum}.*")) { - content = content.replaceAll("\\P{Alnum}", ""); + if (content != null && NON_ALPHANUMERIC_UNICODE.matcher(content).find()) { + content = NON_ALPHANUMERIC_UNICODE.matcher(content).replaceAll(""); } return content; } @@ -2084,10 +2090,8 @@ public ModelsMap postProcessModels(ModelsMap objs) { // parse lombok additional model type annotations Map lombokOptions = new HashMap<>(); - String regexp = "@lombok.(\\w+\\.)*(?\\w+)(\\(.*?\\))?"; - Pattern pattern = Pattern.compile(regexp); for (String annotation : additionalModelTypeAnnotations) { - Matcher matcher = pattern.matcher(annotation); + Matcher matcher = LOMBOK_ANNOTATION.matcher(annotation); if (matcher.find()) { String className = matcher.group("ClassName"); lombokOptions.put(className, true); @@ -2106,10 +2110,9 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List> imports = objs.getImports(); - Pattern pattern = Pattern.compile("java\\.util\\.(List|ArrayList|Map|HashMap)"); for (Iterator> itr = imports.iterator(); itr.hasNext(); ) { String itrImport = itr.next().get("import"); - if (pattern.matcher(itrImport).matches()) { + if (JAVA_UTIL_IMPORT.matcher(itrImport).matches()) { itr.remove(); } } @@ -2253,9 +2256,9 @@ public String toEnumVarName(String value, String datatype) { if ("Integer".equals(datatype) || "Long".equals(datatype) || "Float".equals(datatype) || "Double".equals(datatype) || "BigDecimal".equals(datatype)) { String varName = "NUMBER_" + value; - varName = varName.replaceAll("-", "MINUS_"); - varName = varName.replaceAll("\\+", "PLUS_"); - varName = varName.replaceAll("\\.", "_DOT_"); + varName = varName.replace("-", "MINUS_"); + varName = varName.replace("+", "PLUS_"); + varName = varName.replace(".", "_DOT_"); return varName; } @@ -2264,7 +2267,7 @@ public String toEnumVarName(String value, String datatype) { switch (getEnumPropertyNaming()) { case legacy: // legacy ,e.g. WITHNUMBER1 - var = value.replaceAll("\\W+", "_").toUpperCase(Locale.ROOT); + var = NON_WORD_PLUS.matcher(value).replaceAll("_").toUpperCase(Locale.ROOT); break; case original: // keep value as it is, if meets language naming convention @@ -2277,10 +2280,10 @@ public String toEnumVarName(String value, String datatype) { break; default: // default to MACRO_CASE, e.g. WITH_NUMBER1 - var = underscore(value.replaceAll("\\W+", "_")).toUpperCase(Locale.ROOT); + var = underscore(NON_WORD_PLUS.matcher(value).replaceAll("_")).toUpperCase(Locale.ROOT); break; } - if (var.matches("\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(var).matches()) { var = "_" + var; } return this.toVarName(var); @@ -2357,7 +2360,7 @@ private static CodegenModel reconcileInlineEnums(CodegenModel codegenModel, Code private static String sanitizePackageName(String packageName) { packageName = packageName.trim(); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. - packageName = packageName.replaceAll("[^a-zA-Z0-9_\\.]", "_"); + packageName = INVALID_PACKAGE_CHARS.matcher(packageName).replaceAll("_"); if (Strings.isNullOrEmpty(packageName)) { return "invalidPackageName"; } @@ -2366,7 +2369,7 @@ private static String sanitizePackageName(String packageName) { private String sanitizePath(String p) { //prefer replace a ", instead of a fuLL URL encode for readability - return p.replaceAll("\"", "%22"); + return p.replace("\"", "%22"); } @Override @@ -2468,7 +2471,7 @@ public String sanitizeTag(String tag) { tag = camelize(underscore(sanitizeName(tag))); // tag starts with numbers - if (tag.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(tag).matches()) { tag = "Class" + tag; } return tag; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJuliaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJuliaCodegen.java index ea5a868b2fa0..c486279217eb 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJuliaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJuliaCodegen.java @@ -40,12 +40,18 @@ import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.regex.Pattern; import static org.openapitools.codegen.utils.StringUtils.camelize; public abstract class AbstractJuliaCodegen extends DefaultCodegen { protected final Logger LOGGER = LoggerFactory.getLogger(AbstractJuliaCodegen.class); + private static final Pattern SANITIZE_NON_ALPHANUM_BRACE = Pattern.compile("[^a-zA-Z0-9_{}]"); + private static final Pattern ALNUM_UNDERSCORE = Pattern.compile("[a-zA-Z0-9_]*"); + private static final Pattern VAR_QUOTED = Pattern.compile("var\".*\""); + private static final Pattern DOUBLE_BACKSLASH = Pattern.compile("\\\\\\\\"); + protected String srcPath = "src"; protected String apiSrcPath = srcPath + "/apis/"; protected String modelSrcPath = srcPath + "/models/"; @@ -157,7 +163,7 @@ public GeneratorLanguage generatorLanguage() { } protected static String dropDots(String str) { - return str.replaceAll("\\.", "_"); + return str.replace(".", "_"); } /** @@ -213,7 +219,7 @@ public String toModelDocFilename(String name) { @Override public String toApiFilename(String name) { - name = name.replaceAll("-", "_"); + name = name.replace("-", "_"); return "api_" + camelize(name) + "Api"; } @@ -278,20 +284,20 @@ public String sanitizeName(String name) { return "value"; } - name = name.replaceAll("\\[\\]", ""); - name = name.replaceAll("\\[", "_"); - name = name.replaceAll("\\]", ""); - name = name.replaceAll("\\(", "_"); - name = name.replaceAll("\\)", ""); - name = name.replaceAll("\\.", "_"); - name = name.replaceAll("-", "_"); - name = name.replaceAll(" ", "_"); - name = name.replaceAll("/", "_"); - return name.replaceAll("[^a-zA-Z0-9_{}]", ""); + name = name.replace("[]", ""); + name = name.replace("[", "_"); + name = name.replace("]", ""); + name = name.replace("(", "_"); + name = name.replace(")", ""); + name = name.replace(".", "_"); + name = name.replace("-", "_"); + name = name.replace(" ", "_"); + name = name.replace("/", "_"); + return SANITIZE_NON_ALPHANUM_BRACE.matcher(name).replaceAll(""); } protected boolean needsVarEscape(String name) { - return (!name.matches("[a-zA-Z0-9_]*") && !name.matches("var\".*\"")) || reservedWords.contains(name); + return (!ALNUM_UNDERSCORE.matcher(name).matches() && !VAR_QUOTED.matcher(name).matches()) || reservedWords.contains(name); } /** @@ -310,7 +316,7 @@ public String toModelName(final String name) { String result = sanitizeName(name); // remove dollar sign - result = result.replaceAll("$", ""); + result = result.replace("$", ""); // model name cannot use reserved keyword, e.g. return if (isReservedWord(result)) { @@ -319,7 +325,7 @@ public String toModelName(final String name) { } // model name starts with number - if (result.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(result).matches()) { LOGGER.warn(result + " (model name starts with number) cannot be used as model name. Renamed to " + camelize("model_" + result)); result = "model_" + result; // e.g. 200Response => Model200Response (after camelize) } @@ -514,7 +520,7 @@ public String toRegularExpression(String pattern) { pattern = escapeText(pattern); // escapeText unnecessarily escapes `\` such that `\.` in the regex ends up as `\\.` for example. // we need to restore it back by converting `\\` to `\` - pattern = pattern.replaceAll("\\\\\\\\", "\\\\"); + pattern = DOUBLE_BACKSLASH.matcher(pattern).replaceAll("\\\\"); return pattern; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java index 545278224b99..014ddf6eb1ac 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractKotlinCodegen.java @@ -50,6 +50,11 @@ public abstract class AbstractKotlinCodegen extends DefaultCodegen implements CodegenConfig { + private static final Pattern NON_WORD_UNICODE = Pattern.compile("\\W+", Pattern.UNICODE_CHARACTER_CLASS); + private static final Pattern ALL_UNDERSCORES = Pattern.compile("^_*$"); + private static final Pattern LEADING_DIGIT_OR_DOLLAR = Pattern.compile("(^\\d.*)|(.*\\$.*)"); + private static final Pattern UNDERSCORE_CLASS = Pattern.compile("^_*class$"); + public static final String MODEL_MUTABLE = "modelMutable"; public static final String MODEL_MUTABLE_DESC = "Create mutable models"; public static final String ADDITIONAL_MODEL_TYPE_ANNOTATIONS = "additionalModelTypeAnnotations"; @@ -622,7 +627,7 @@ public String toEnumVarName(String value, String datatype) { if (value.isEmpty()) { modified = "EMPTY"; } else { - modified = value.replaceAll("-", "_"); + modified = value.replace("-", "_"); modified = sanitizeKotlinSpecificNames(modified); } @@ -721,7 +726,7 @@ public String toModelName(final String name) { return importMapping.get(name); } - String modifiedName = name.replaceAll("\\.", "").replaceAll("-", "_"); + String modifiedName = name.replace(".", "").replace("-", "_"); String nameWithPrefixSuffix = sanitizeKotlinSpecificNames(modifiedName); if (!StringUtils.isEmpty(modelNamePrefix)) { @@ -745,7 +750,7 @@ public String toModelName(final String name) { } // model name starts with number - if (modifiedName.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(modifiedName).matches()) { final String modelName = "Model" + modifiedName; // e.g. 200Response => Model200Response (after camelize) LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, modelName); @@ -778,7 +783,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { LOGGER.warn(operationId + " (starting with a number) cannot be used as method name. Renamed to " + camelize("call_" + operationId), LOWERCASE_FIRST_LETTER); operationId = camelize("call_" + operationId, LOWERCASE_FIRST_LETTER); } @@ -805,14 +810,14 @@ private String sanitizeKotlinSpecificNames(final String name) { } // Fallback, replace unknowns with underscore. - word = Pattern.compile("\\W+", Pattern.UNICODE_CHARACTER_CLASS).matcher(word).replaceAll("_"); - if (word.matches("\\d.*")) { + word = NON_WORD_UNICODE.matcher(word).replaceAll("_"); + if (STARTS_WITH_DIGIT.matcher(word).matches()) { word = "_" + word; } // _, __, and ___ are reserved in Kotlin. Treat all names with only underscores consistently, regardless of count. - if (word.matches("^_*$")) { - word = word.replaceAll("\\Q_\\E", "Underscore"); + if (ALL_UNDERSCORES.matcher(word).matches()) { + word = word.replace("_", "Underscore"); } return word; @@ -1019,7 +1024,7 @@ protected String toVariableName(String name) { name = sanitizeName(name, "\\W-[\\$]"); name = sanitizeKotlinSpecificNames(name); - if (name.toLowerCase(Locale.ROOT).matches("^_*class$")) { + if (UNDERSCORE_CLASS.matcher(name.toLowerCase(Locale.ROOT)).matches()) { return "propertyClass"; } @@ -1028,7 +1033,7 @@ protected String toVariableName(String name) { } // if it's all upper case, do nothing - if (name.matches("^[A-Z0-9_]*$")) { + if (ALL_UPPER_UNDERSCORE_DIGITS.matcher(name).matches()) { return name; } @@ -1049,7 +1054,7 @@ protected String toVariableName(String name) { name = camelize(name, LOWERCASE_FIRST_LETTER); // for reserved word or word starting with number or containing dollar symbol, escape it - if (isReservedWord(name) || name.matches("(^\\d.*)|(.*[$].*)")) { + if (isReservedWord(name) || LEADING_DIGIT_OR_DOLLAR.matcher(name).matches()) { name = escapeReservedWord(name); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPhpCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPhpCodegen.java index 68520b123d99..41095056de8e 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPhpCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPhpCodegen.java @@ -46,6 +46,13 @@ public abstract class AbstractPhpCodegen extends DefaultCodegen implements Codeg private final Logger LOGGER = LoggerFactory.getLogger(AbstractPhpCodegen.class); + private static final Pattern TRAILING_SLASH_BACKSLASH = Pattern.compile("[\\\\/]?$"); + private static final Pattern PATH_SEPARATOR_CHARS = Pattern.compile("[\\.\\\\/]"); + private static final Pattern LEADING_AT = Pattern.compile("^@"); + private static final Pattern NON_WORD_BACKSLASH = Pattern.compile("[^\\w\\\\]+"); + private static final Pattern STARTS_WITH_BACKSLASH = Pattern.compile("^\\\\.*"); + private static final Pattern COMPOSER_PACKAGE_NAME = Pattern.compile("^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*$"); + public static final String VARIABLE_NAMING_CONVENTION = "variableNamingConvention"; public static final String PACKAGE_NAME = "packageName"; public static final String SRC_BASE_PATH = "srcBasePath"; @@ -295,13 +302,13 @@ public String toSrcPath(final String packageName, final String basePath) { String modifiedPackageName = packageName.replace(invokerPackage, ""); String modifiedBasePath = basePath; if (basePath != null && !basePath.isEmpty()) { - modifiedBasePath = basePath.replaceAll("[\\\\/]?$", "") + File.separator; + modifiedBasePath = TRAILING_SLASH_BACKSLASH.matcher(basePath).replaceAll("") + File.separator; } // Trim prefix file separators from package path String packagePath = StringUtils.removeStart( // Replace period, backslash, forward slash with file separator in package name - modifiedPackageName.replaceAll("[\\.\\\\/]", Matcher.quoteReplacement("/")), + PATH_SEPARATOR_CHARS.matcher(modifiedPackageName).replaceAll(Matcher.quoteReplacement("/")), File.separator ); @@ -444,7 +451,7 @@ public String toVarName(String name) { // translate @ for properties (like @type) to at_. // Otherwise an additional "type" property will lead to duplicates - name = name.replaceAll("^@", "at_"); + name = LEADING_AT.matcher(name).replaceAll("at_"); // sanitize name name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. @@ -465,7 +472,7 @@ public String toVarName(String name) { // parameter name starting with number won't compile // need to escape it by appending _ at the beginning - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { name = "_" + name; } @@ -485,10 +492,10 @@ public String toParamName(String name) { private String toGenericName(String name) { // remove [ - name = name.replaceAll("\\]", ""); + name = name.replace("]", ""); // Note: backslash ("\\") is allowed for e.g. "\\DateTime" - name = name.replaceAll("[^\\w\\\\]+", "_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + name = NON_WORD_BACKSLASH.matcher(name).replaceAll("_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. // remove dollar sign name = name.replace("$", ""); @@ -500,7 +507,7 @@ private String toGenericName(String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, camelize("model_" + name)); name = "model_" + name; // e.g. 200Response => Model200Response (after camelize) @@ -525,7 +532,7 @@ public String toModelName(String name) { name = toGenericName(name); // add prefix and/or suffix only if name does not start with \ (e.g. \DateTime) - if (!name.matches("^\\\\.*")) { + if (!STARTS_WITH_BACKSLASH.matcher(name).matches()) { if (!StringUtils.isEmpty(modelNamePrefix)) { name = modelNamePrefix + "_" + name; } @@ -598,7 +605,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, camelize(sanitizeName("call_" + operationId), LOWERCASE_FIRST_LETTER)); operationId = "call_" + operationId; } @@ -764,20 +771,20 @@ public String toEnumVarName(String name, String datatype) { // number if ("int".equals(datatype) || "float".equals(datatype)) { - if (name.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(name).matches()) { // starts with number name = "NUMBER_" + name; } - name = name.replaceAll("-", "MINUS_"); - name = name.replaceAll("\\+", "PLUS_"); - name = name.replaceAll("\\.", "_DOT_"); + name = name.replace("-", "MINUS_"); + name = name.replace("+", "PLUS_"); + name = name.replace(".", "_DOT_"); } // string String enumName = sanitizeName(underscore(name).toUpperCase(Locale.ROOT)); - enumName = enumName.replaceFirst("^_", ""); - enumName = enumName.replaceFirst("_$", ""); + enumName = FIRST_LEADING_UNDERSCORE.matcher(enumName).replaceFirst(""); + enumName = LAST_TRAILING_UNDERSCORE.matcher(enumName).replaceFirst(""); - if (isReservedWord(enumName) || enumName.matches("\\d.*")) { // reserved word or starts with number + if (isReservedWord(enumName) || STARTS_WITH_DIGIT.matcher(enumName).matches()) { // reserved word or starts with number return escapeReservedWord(enumName); } else { return enumName; @@ -795,7 +802,7 @@ public String toEnumName(CodegenProperty property) { // remove [] for array or map of enum enumName = enumName.replace("[]", ""); - if (enumName.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(enumName).matches()) { // starts with number return "_" + enumName; } else { return enumName; @@ -907,7 +914,7 @@ public String getComposerPackageName() { if ( packageName.contentEquals("/") || packageName.contentEquals("null/null") - || !Pattern.matches("^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*$", packageName) + || !COMPOSER_PACKAGE_NAME.matcher(packageName).matches() ) { return ""; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java index a4c775057c47..09a6cdf64e87 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPythonCodegen.java @@ -49,6 +49,8 @@ public abstract class AbstractPythonCodegen extends DefaultCodegen implements CodegenConfig { private final Logger LOGGER = LoggerFactory.getLogger(AbstractPythonCodegen.class); + private static final Pattern REGEX_VALUE_EXTRACTOR = Pattern.compile("^/\\^?(.+?)\\$?/.?$"); + public static final String MAP_NUMBER_TO = "mapNumberTo"; protected String packageName = "openapi_client"; @@ -220,7 +222,7 @@ public String toDefaultValue(Schema p) { if (defaultValue != null) { defaultValue = defaultValue.replace("\\", "\\\\") .replace("'", "\\'"); - if (Pattern.compile("\r\n|\r|\n").matcher(defaultValue).find()) { + if (MULTILINE_STRING.matcher(defaultValue).find()) { return "'''" + defaultValue + "'''"; } else { return "'" + defaultValue + "'"; @@ -252,7 +254,7 @@ public String toVarName(String name) { name = name.replace("$", ""); // if it's all upper case, convert to lower case - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { name = name.toLowerCase(Locale.ROOT); } @@ -261,10 +263,10 @@ public String toVarName(String name) { name = underscore(name); // remove leading underscore - name = name.replaceAll("^_*", ""); + name = LEADING_UNDERSCORES.matcher(name).replaceAll(""); // for reserved word or word starting with number, append _ - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } @@ -306,7 +308,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, underscore(sanitizeName("call_" + operationId))); operationId = "call_" + operationId; } @@ -438,8 +440,7 @@ private String toExampleValueRecursive(Schema schema, List includedSchem Random random = new Random(18); String sample = rgxGen.generate(random); // omit leading / and trailing /, omit trailing /i - Pattern valueExtractor = Pattern.compile("^/\\^?(.+?)\\$?/.?$"); - Matcher m = valueExtractor.matcher(sample); + Matcher m = REGEX_VALUE_EXTRACTOR.matcher(sample); if (m.find()) { example = m.group(m.groupCount()); } else { @@ -750,7 +751,7 @@ public String toModelName(String name) { // remove dollar sign sanitizedName = sanitizedName.replace("$", ""); // remove whitespace - sanitizedName = sanitizedName.replaceAll("\\s+", ""); + sanitizedName = WHITESPACE.matcher(sanitizedName).replaceAll(""); String nameWithPrefixSuffix = sanitizedName; if (!StringUtils.isEmpty(modelNamePrefix)) { @@ -776,7 +777,7 @@ public String toModelName(String name) { } // model name starts with number - if (camelizedName.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(camelizedName).matches()) { String modelName = "Model" + camelizedName; // e.g. return => ModelReturn (after camelize) LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", camelizedName, modelName); schemaKeyToModelNameCache.put(origName, modelName); @@ -821,7 +822,7 @@ public String toApiVarName(String name) { } protected static String dropDots(String str) { - return str.replaceAll("\\.", "_"); + return str.replace(".", "_"); } @Override @@ -1109,10 +1110,10 @@ public String toEnumVariableName(String name, String datatype) { } name = name.replace(" ", "_"); - name = name.replaceFirst("^_", ""); - name = name.replaceFirst("_$", ""); + name = FIRST_LEADING_UNDERSCORE.matcher(name).replaceFirst(""); + name = LAST_TRAILING_UNDERSCORE.matcher(name).replaceFirst(""); - if (name.matches("\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { name = "ENUM_" + name.toUpperCase(Locale.ROOT); } @@ -1381,9 +1382,9 @@ public String addRegularExpressionDelimiter(String pattern) { return pattern; } - if (!pattern.matches("^/.*")) { + if (!STARTS_WITH_SLASH.matcher(pattern).matches()) { // Perform a negative lookbehind on each `/` to ensure that it is escaped. - return "/" + pattern.replaceAll("(? regexModifiers; @@ -190,7 +194,7 @@ public String toDefaultValue(Schema p) { if (defaultValue != null) { defaultValue = defaultValue.replace("\\", "\\\\") .replace("'", "\'"); - if (Pattern.compile("\r\n|\r|\n").matcher(defaultValue).find()) { + if (MULTILINE_STRING.matcher(defaultValue).find()) { return "'''" + defaultValue + "'''"; } else { return "'" + defaultValue + "'"; @@ -223,7 +227,7 @@ public String toVarName(String name) { name = name.replace("$", ""); // if it's all upper case, convert to lower case - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { name = name.toLowerCase(Locale.ROOT); } @@ -232,10 +236,10 @@ public String toVarName(String name) { name = underscore(name); // remove leading underscore - name = name.replaceAll("^_*", ""); + name = LEADING_UNDERSCORES.matcher(name).replaceAll(""); // for reserved word or word starting with number, append _ - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } @@ -277,7 +281,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, underscore(sanitizeName("call_" + operationId))); operationId = "call_" + operationId; } @@ -409,8 +413,7 @@ private String toExampleValueRecursive(Schema schema, List includedSchem Random random = new Random(18); String sample = rgxGen.generate(random); // omit leading / and trailing /, omit trailing /i - Pattern valueExtractor = Pattern.compile("^/\\^?(.+?)\\$?/.?$"); - Matcher m = valueExtractor.matcher(sample); + Matcher m = REGEX_VALUE_EXTRACTOR.matcher(sample); if (m.find()) { example = m.group(m.groupCount()); } else { @@ -702,7 +705,7 @@ public String toModelName(String name) { // remove dollar sign sanitizedName = sanitizedName.replace("$", ""); // remove whitespace - sanitizedName = sanitizedName.replaceAll("\\s+", ""); + sanitizedName = WHITESPACE.matcher(sanitizedName).replaceAll(""); String nameWithPrefixSuffix = sanitizedName; if (!StringUtils.isEmpty(modelNamePrefix)) { @@ -728,7 +731,7 @@ public String toModelName(String name) { } // model name starts with number - if (camelizedName.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(camelizedName).matches()) { String modelName = "Model" + camelizedName; // e.g. return => ModelReturn (after camelize) LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", camelizedName, modelName); schemaKeyToModelNameCache.put(origName, modelName); @@ -773,7 +776,7 @@ public String toApiVarName(String name) { } protected static String dropDots(String str) { - return str.replaceAll("\\.", "_"); + return DOT.matcher(str).replaceAll("_"); } @Override @@ -1625,10 +1628,10 @@ public String toEnumVariableName(String name, String datatype) { } name = name.replace(" ", "_"); - name = name.replaceFirst("^_", ""); - name = name.replaceFirst("_$", ""); + name = FIRST_LEADING_UNDERSCORE.matcher(name).replaceFirst(""); + name = LAST_TRAILING_UNDERSCORE.matcher(name).replaceFirst(""); - if (name.matches("\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { name = "ENUM_" + name.toUpperCase(Locale.ROOT); } @@ -1929,9 +1932,9 @@ public String addRegularExpressionDelimiter(String pattern) { return pattern; } - if (!pattern.matches("^/.*")) { + if (!STARTS_WITH_SLASH.matcher(pattern).matches()) { // Perform a negative lookbehind on each `/` to ensure that it is escaped. - return "/" + pattern.replaceAll("(? charactersToAllow = Collections.singletonList("_"); @@ -184,7 +188,7 @@ public String sanitizeIdentifier(String name, CasingType casingType, String esca } // Replace hyphens and periods with underscores - name = name.replaceAll("[\\.\\-]", "_"); + name = HYPHEN_OR_PERIOD.matcher(name).replaceAll("_"); // Apply special character escapes, e.g. "@type" => "At_type" // Remove the trailing underscore if necessary @@ -207,7 +211,7 @@ public String sanitizeIdentifier(String name, CasingType casingType, String esca // If word starts with number add a prefix // Note: this must be done after casing since CamelCase will strip leading underscores - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { nameWasModified = true; name = casingFunction.apply(escapePrefix + '_' + name); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractScalaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractScalaCodegen.java index d453d882e57b..f93800dc53a2 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractScalaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractScalaCodegen.java @@ -37,6 +37,7 @@ import java.io.StringWriter; import java.io.Writer; import java.util.*; +import java.util.regex.Pattern; import static org.openapitools.codegen.languages.AbstractJavaCodegen.DATE_LIBRARY; import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER; @@ -47,6 +48,11 @@ public abstract class AbstractScalaCodegen extends DefaultCodegen { private final Logger LOGGER = LoggerFactory.getLogger(AbstractScalaCodegen.class); + /** Matches a valid Java/Scala identifier starting with a letter, {@code _} or {@code $}. */ + private static final Pattern SCALA_IDENTIFIER = Pattern.compile("[a-zA-Z_$][\\w_$]+"); + /** Matches a string consisting only of digits (possibly empty). */ + private static final Pattern DIGITS_ZERO_OR_MORE = Pattern.compile("[0-9]*"); + // https://spec.openapis.org/registry/format/date-time-local.html protected static final String DATE_TIME_LOCAL_FORMAT = "date-time-local"; @@ -288,11 +294,11 @@ public String toVarName(String name) { } // if it's all upper case, do nothing - if (!varName.matches("^[A-Z_0-9]*$")) { + if (!ALL_UPPER_UNDERSCORE_DIGITS.matcher(varName).matches()) { varName = getNameUsingModelPropertyNaming(varName); } - if (isReservedWord(varName) || varName.matches("^\\d.*")) { + if (isReservedWord(varName) || STARTS_WITH_DIGIT.matcher(varName).matches()) { varName = escapeReservedWord(varName); } @@ -502,7 +508,7 @@ public String toModelName(final String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { final String modelName = "Model" + camelizedName; // e.g. 200Response => Model200Response (after camelize) LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, modelName); @@ -531,13 +537,13 @@ protected String formatIdentifier(String name, boolean capitalized) { if (capitalized) { identifier = StringUtils.capitalize(identifier); } - if (identifier.matches("[a-zA-Z_$][\\w_$]+") && !isReservedWord(identifier)) { + if (SCALA_IDENTIFIER.matcher(identifier).matches() && !isReservedWord(identifier)) { return identifier; } // below code block only for scala-sttp4-jsoniter for backward copmatibility if (this instanceof ScalaSttp4JsoniterClientCodegen) { - if (identifier.matches("[0-9]*")) { + if (DIGITS_ZERO_OR_MORE.matcher(identifier).matches()) { return escapeReservedWord(identifier); } if (!capitalized || StringUtils.isNumeric(name)) { @@ -601,7 +607,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { LOGGER.warn(operationId + " (starting with a number) cannot be used as method sname. Renamed to " + camelize("call_" + operationId), true); operationId = camelize("call_" + operationId, LOWERCASE_FIRST_LETTER); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractTypeScriptClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractTypeScriptClientCodegen.java index 9f09d9c5cb14..d08229ca2e2e 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractTypeScriptClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractTypeScriptClientCodegen.java @@ -79,6 +79,11 @@ public abstract class AbstractTypeScriptClientCodegen extends DefaultCodegen implements CodegenConfig { + /** Splits a composed TypeScript type string on {@code |}, {@code &}, {@code <}, or {@code >}. */ + private static final Pattern COMPOSED_TYPE_DELIMITERS = Pattern.compile("[|&<>]"); + /** Matches NPM versions that end with {@code -SNAPSHOT}. */ + private static final Pattern SNAPSHOT_VERSION = Pattern.compile("^.*-SNAPSHOT$"); + /** * Help generating code for any kind of URL parameters (path, matrix, query). *

@@ -491,7 +496,7 @@ public Map toModelImportMap(String name) { } private String[] splitComposedType(String name) { - return name.replace(" ", "").split("[|&<>]"); + return COMPOSED_TYPE_DELIMITERS.split(name.replace(" ", "")); } private boolean isUnionType(String name) { @@ -526,7 +531,7 @@ public void preprocessOpenAPI(OpenAPI openAPI) { } if (additionalProperties.containsKey(SNAPSHOT) && Boolean.parseBoolean(additionalProperties.get(SNAPSHOT).toString())) { - if (npmVersion.toUpperCase(Locale.ROOT).matches("^.*-SNAPSHOT$")) { + if (SNAPSHOT_VERSION.matcher(npmVersion.toUpperCase(Locale.ROOT)).matches()) { this.setNpmVersion(npmVersion + "." + SNAPSHOT_SUFFIX_FORMAT.get().format(new Date())); } else { this.setNpmVersion(npmVersion + "-SNAPSHOT." + SNAPSHOT_SUFFIX_FORMAT.get().format(new Date())); @@ -601,7 +606,7 @@ public String toVarName(String name) { } private String toSafeIdentifier(String name) { - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } return name; @@ -650,7 +655,7 @@ protected String toTypescriptTypeName(final String name, String safePrefix) { } // model name starts with number - if (sanName.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(sanName).matches()) { String modelName = safePrefix + sanName; // e.g. 200Response => Model200Response LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", sanName, modelName); @@ -961,25 +966,25 @@ public String toEnumVarName(String name, String datatype) { if ("number".equals(datatype)) { varName = "NUMBER_" + varName; - varName = varName.replaceAll("-", "MINUS_"); - varName = varName.replaceAll("\\+", "PLUS_"); - varName = varName.replaceAll("\\.", "_DOT_"); + varName = MINUS.matcher(varName).replaceAll("MINUS_"); + varName = PLUS.matcher(varName).replaceAll("PLUS_"); + varName = DOT.matcher(varName).replaceAll("_DOT_"); return varName; } // string if (isEnumPropertyNamingReplaceSpecialChar()) { - varName = varName.replaceAll("-", "_minus_"); - varName = varName.replaceAll("\\+", "_plus_"); - varName = varName.replaceAll("_+", "_"); + varName = MINUS.matcher(varName).replaceAll("_minus_"); + varName = PLUS.matcher(varName).replaceAll("_plus_"); + varName = MULTI_UNDERSCORES.matcher(varName).replaceAll("_"); } varName = sanitizeName(varName); - varName = varName.replaceFirst("^_", ""); - varName = varName.replaceFirst("_$", ""); + varName = FIRST_LEADING_UNDERSCORE.matcher(varName).replaceFirst(""); + varName = LAST_TRAILING_UNDERSCORE.matcher(varName).replaceFirst(""); varName = getNameUsingEnumPropertyNaming(varName); - if (varName.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(varName).matches()) { // starts with number return "_" + varName; } else { return varName; @@ -1207,7 +1212,7 @@ public String escapeText(String input) { } // replace ' with \' - return super.escapeText(input).replace("\'", "\\\'"); + return super.escapeText(input).replace("'", "\\'"); } @Override diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AdaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AdaCodegen.java index 45c87f8b1c3b..4ca6694e8355 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AdaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AdaCodegen.java @@ -103,7 +103,7 @@ public void processOpts() { } else { // default: set project based on package name // e.g. petstore.api (package name) => petstore_api (project name) - projectName = packageName.replaceAll("\\.", "_"); + projectName = DOT.matcher(packageName).replaceAll("_"); } String configBaseName = modelPackage.toLowerCase(Locale.ROOT); supportingFiles.add(new SupportingFile("gnat-project.mustache", "", toFilename(projectName) + ".gpr")); @@ -125,7 +125,7 @@ public void processOpts() { additionalProperties.put("httpClientGprName", httpClientPackageName.toLowerCase(Locale.ROOT)); additionalProperties.put(CodegenConstants.PROJECT_NAME, projectName); - String[] names = this.modelPackage.split("\\."); + String[] names = DOT.split(this.modelPackage); String pkgName = names[0]; additionalProperties.put("packageLevel1", pkgName); supportingFiles.add(new SupportingFile("package-spec-level1.mustache", "src", @@ -146,8 +146,8 @@ public void processOpts() { @Override public void execute(Template.Fragment fragment, Writer writer) throws IOException { String content = fragment.execute(); - content = content.trim().replaceAll("\n$", ""); - writer.write(content.replaceAll("\n", "\n -- ")); + content = TRAILING_NEWLINE.matcher(content.trim()).replaceAll(""); + writer.write(NEWLINE.matcher(content).replaceAll("\n -- ")); } }); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AdaServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AdaServerCodegen.java index afc3690d21e0..300ebc4254b3 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AdaServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AdaServerCodegen.java @@ -108,7 +108,7 @@ public void processOpts() { } else { // default: set project based on package name // e.g. petstore.api (package name) => petstore_api (project name) - projectName = packageName.replaceAll("\\.", "_"); + projectName = DOT.matcher(packageName).replaceAll("_"); } String configBaseName = modelPackage.toLowerCase(Locale.ROOT); supportingFiles.add(new SupportingFile("gnat-project.mustache", "", toFilename(projectName) + ".gpr")); @@ -131,7 +131,7 @@ public void processOpts() { additionalProperties.put("httpClientGprName", httpClientPackageName.toLowerCase(Locale.ROOT)); additionalProperties.put(CodegenConstants.PROJECT_NAME, projectName); - String names[] = this.modelPackage.split("\\."); + String names[] = DOT.split(this.modelPackage); String pkgName = names[0]; additionalProperties.put("packageLevel1", pkgName); supportingFiles.add(new SupportingFile("package-spec-level1.mustache", "src", @@ -152,8 +152,8 @@ public void processOpts() { @Override public void execute(Template.Fragment fragment, Writer writer) throws IOException { String content = fragment.execute(); - content = content.trim().replaceAll("\n$", ""); - writer.write(content.replaceAll("\n", "\n -- ")); + content = TRAILING_NEWLINE.matcher(content.trim()).replaceAll(""); + writer.write(NEWLINE.matcher(content).replaceAll("\n -- ")); } }); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AndroidClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AndroidClientCodegen.java index b94c939f8e1a..f6a54a0f5071 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AndroidClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AndroidClientCodegen.java @@ -260,10 +260,10 @@ public String toVarName(String name) { name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + name = MINUS.matcher(name).replaceAll("_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } @@ -272,7 +272,7 @@ public String toVarName(String name) { name = camelize(name, LOWERCASE_FIRST_LETTER); // for reserved word or word starting with number, append _ - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } @@ -308,7 +308,7 @@ public String toModelName(String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { String modelName = "Model" + name; // e.g. 200Response => Model200Response (after camelize) LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, modelName); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AsciidocDocumentationCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AsciidocDocumentationCodegen.java index 42f56a8bcce2..14299508433f 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AsciidocDocumentationCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AsciidocDocumentationCodegen.java @@ -98,7 +98,7 @@ public void execute(final Template.Fragment frag, final Writer out) throws IOExc } private String escapeCurlyBrackets(String relativeFileName) { - return relativeFileName.replaceAll("\\{", "\\\\{").replaceAll("\\}", "\\\\}"); + return RIGHT_CURLY_BRACE.matcher(LEFT_CURLY_BRACE.matcher(relativeFileName).replaceAll("\\\\{")).replaceAll("\\\\}"); } } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AvroSchemaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AvroSchemaCodegen.java index 572ec6f64777..5b6dab70737b 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AvroSchemaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AvroSchemaCodegen.java @@ -252,7 +252,7 @@ protected List> buildEnumVars(List values, String da */ private String sanitizeEnumValue(String value) { // Replace any non-alphanumeric characters with an underscore - String sanitizedValue = value.replaceAll("[^A-Za-z0-9_]", "_"); + String sanitizedValue = NON_WORD_CHAR.matcher(value).replaceAll("_"); // If the enum starts with a number, prefix it with an underscore sanitizedValue = sanitizedValue.replaceAll("^([0-9])", "_$1"); return sanitizedValue; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/BashClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/BashClientCodegen.java index 2fa2b92d03fd..0d0ae19e9299 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/BashClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/BashClientCodegen.java @@ -58,6 +58,18 @@ public class BashClientCodegen extends DefaultCodegen implements CodegenConfig { protected static int emptyMethodNameCounter = 0; + // --- Precompiled patterns for markdown-to-bash conversion --- + private static final java.util.regex.Pattern MD_BOLD_STAR = java.util.regex.Pattern.compile("(?m)(^|\\s)\\*{2}([\\w ]+)\\*{2}($|\\s)"); + private static final java.util.regex.Pattern MD_BOLD_UNDERSCORE = java.util.regex.Pattern.compile("(?m)(^|\\s)_{2}([\\w ]+)_{2}($|\\s)"); + private static final java.util.regex.Pattern MD_ITALIC_STAR = java.util.regex.Pattern.compile("(?m)(^|\\s)\\*([\\w ]+)\\*($|\\s)"); + private static final java.util.regex.Pattern MD_ITALIC_UNDER = java.util.regex.Pattern.compile("(?m)(^|\\s)_([\\w ]+)_($|\\s)"); + private static final java.util.regex.Pattern MD_H1 = java.util.regex.Pattern.compile("(?m)^#\\s+(.+)$"); + private static final java.util.regex.Pattern MD_H2 = java.util.regex.Pattern.compile("(?m)^##\\s+(.+)$"); + private static final java.util.regex.Pattern MD_H3 = java.util.regex.Pattern.compile("(?m)^###\\s+(.+)$"); + private static final java.util.regex.Pattern MD_CODE_BACKTICK = java.util.regex.Pattern.compile("(?m)\\s*```.*$"); + private static final java.util.regex.Pattern MD_CODE_QUOTE = java.util.regex.Pattern.compile("(?m)\\s*'''.*$"); + private static final java.util.regex.Pattern MD_TRAILING_WS = java.util.regex.Pattern.compile("\\s+$"); + public static final String CURL_OPTIONS = "curlOptions"; public static final String PROCESS_MARKDOWN = "processMarkdown"; public static final String SCRIPT_NAME = "scriptName"; @@ -510,56 +522,45 @@ public String escapeText(String input) { * Convert markdown strong **Bold text** and __Bold text__ * to bash bold control sequences (tput bold) */ - result = result.replaceAll("(?m)(^|\\s)\\*{2}([\\w\\d ]+)\\*{2}($|\\s)", - "\\$\\(tput bold\\) $2 \\$\\(tput sgr0\\)"); + result = MD_BOLD_STAR.matcher(result).replaceAll("\\$\\(tput bold\\) $2 \\$\\(tput sgr0\\)"); + result = MD_BOLD_UNDERSCORE.matcher(result).replaceAll("\\$\\(tput bold\\) $2 \\$\\(tput sgr0\\)"); - result = result.replaceAll("(?m)(^|\\s)_{2}([\\w\\d ]+)_{2}($|\\s)", - "\\$\\(tput bold\\) $2 \\$\\(tput sgr0\\)"); /** * Convert markdown *Italics text* and _Italics text_ to bash dim * control sequences (tput dim) */ - result = result.replaceAll("(?m)(^|\\s)\\*{1}([\\w\\d ]+)\\*{1}($|\\s)", - "\\$\\(tput dim\\) $2 \\$\\(tput sgr0\\)"); - - result = result.replaceAll("(?m)(^|\\s)_{1}([\\w\\d ]+)_{1}($|\\s)", - "\\$\\(tput dim\\) $2 \\$\\(tput sgr0\\)"); + result = MD_ITALIC_STAR.matcher(result).replaceAll("\\$\\(tput dim\\) $2 \\$\\(tput sgr0\\)"); + result = MD_ITALIC_UNDER.matcher(result).replaceAll("\\$\\(tput dim\\) $2 \\$\\(tput sgr0\\)"); /** * Convert all markdown section 1 level headers with bold */ - result = result.replaceAll("(?m)^\\#\\s+(.+)$", - "\n\\$\\(tput bold\\)\\$\\(tput setaf 7\\)" - + "$1\\$\\(tput sgr0\\)"); + result = MD_H1.matcher(result).replaceAll("\n\\$\\(tput bold\\)\\$\\(tput setaf 7\\)" + + "$1\\$\\(tput sgr0\\)"); /** * Convert all markdown section 2 level headers with bold */ - result = result.replaceAll("(?m)^\\#\\#\\s+(.+)$", - "\n\\$\\(tput bold\\)\\$\\(tput setaf 7\\)" - + "$1\\$\\(tput sgr0\\)"); + result = MD_H2.matcher(result).replaceAll("\n\\$\\(tput bold\\)\\$\\(tput setaf 7\\)" + + "$1\\$\\(tput sgr0\\)"); /** * Convert all markdown section 3 level headers with bold */ - result = result.replaceAll("(?m)^\\#\\#\\#\\s+(.+)$", - "\n\\$\\(tput bold\\)\\$\\(tput setaf 7\\)" - + "$1\\$\\(tput sgr0\\)"); + result = MD_H3.matcher(result).replaceAll("\n\\$\\(tput bold\\)\\$\\(tput setaf 7\\)" + + "$1\\$\\(tput sgr0\\)"); /** * Convert all markdown code blocks into --- delimited sections */ - result = result.replaceAll("(?m)\\s*```.*$", - "\n---"); - - result = result.replaceAll("(?m)\\s*\\'\\'\\'.*$", - "\n---"); + result = MD_CODE_BACKTICK.matcher(result).replaceAll("\n---"); + result = MD_CODE_QUOTE.matcher(result).replaceAll("\n---"); /** * Remove any trailing new line at the end of the string */ - result = result.replaceAll("\\s+$", ""); + result = MD_TRAILING_WS.matcher(result).replaceAll(""); } return result; @@ -583,7 +584,7 @@ public String escapeUnsafeCharacters(String input) { /** * Replace backticks with normal single quotes. */ - String result = input.replaceAll("`", "'"); + String result = input.replace("`", "'"); return result; } @@ -787,7 +788,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, underscore(sanitizeName("call_" + operationId))); operationId = "call_" + operationId; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CLibcurlClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CLibcurlClientCodegen.java index 406c32d0f965..b161f5f69d1e 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CLibcurlClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CLibcurlClientCodegen.java @@ -605,14 +605,14 @@ public String toVarName(String name) { // sanitize name name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. // if it's all upper case, convert to lower case - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { name = name.toLowerCase(Locale.ROOT); } name = underscore(name); // for reserved word or word starting with number, append _ - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } @@ -627,10 +627,10 @@ public String toParamName(String name) { } // should be the same as variable name - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } - name = name.replaceAll("-", "_"); + name = MINUS.matcher(name).replaceAll("_"); return name; } @@ -659,7 +659,7 @@ public String toModelName(String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, camelize("model_" + name)); name = "model_" + name; // e.g. 200Response => Model200Response (after camelize) @@ -683,7 +683,7 @@ public String toModelDocFilename(String name) { @Override public String toApiFilename(String name) { // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + name = MINUS.matcher(name).replaceAll("_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. // e.g. PhoneNumberApi.rb => phone_number_api.rb return camelize(name) + "API"; @@ -715,14 +715,14 @@ public String toApiName(String name) { @Override public String toEnumValue(String value, String datatype) { - value = value.replaceAll("-", "_"); + value = MINUS.matcher(value).replaceAll("_"); if (isReservedWord(value)) { value = escapeReservedWord(value); } if ("Integer".equals(datatype) || "Float".equals(datatype)) { return value; } else { - if (value.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(value).matches()) { // starts with number return escapeReservedWord(escapeText(value)); } else { return escapeText(value); @@ -739,18 +739,18 @@ public String toEnumVarName(String name, String datatype) { // number if ("Integer".equals(datatype) || "Float".equals(datatype)) { String varName = name; - varName = varName.replaceAll("-", "MINUS_"); - varName = varName.replaceAll("\\+", "PLUS_"); - varName = varName.replaceAll("\\.", "_DOT_"); + varName = MINUS.matcher(varName).replaceAll("MINUS_"); + varName = PLUS.matcher(varName).replaceAll("PLUS_"); + varName = DOT.matcher(varName).replaceAll("_DOT_"); return varName; } // string String enumName = sanitizeName(camelize(name).toUpperCase(Locale.ROOT)); - enumName = enumName.replaceFirst("^_", ""); - enumName = enumName.replaceFirst("_$", ""); + enumName = FIRST_LEADING_UNDERSCORE.matcher(enumName).replaceFirst(""); + enumName = LAST_TRAILING_UNDERSCORE.matcher(enumName).replaceFirst(""); - if (enumName.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(enumName).matches()) { // starts with number return escapeReservedWord(enumName); } else { return enumName; @@ -760,10 +760,10 @@ public String toEnumVarName(String name, String datatype) { @Override public String toEnumName(CodegenProperty property) { String enumName = camelize(toModelName(property.name)).toUpperCase(Locale.ROOT); - enumName = enumName.replaceFirst("^_", ""); - enumName = enumName.replaceFirst("_$", ""); + enumName = FIRST_LEADING_UNDERSCORE.matcher(enumName).replaceFirst(""); + enumName = LAST_TRAILING_UNDERSCORE.matcher(enumName).replaceFirst(""); - if (enumName.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(enumName).matches()) { // starts with number return escapeReservedWord(enumName); } else { return enumName; @@ -793,7 +793,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { String newOperationId = camelize(sanitizeName("call_" + operationId), LOWERCASE_FIRST_LETTER); LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, newOperationId); return newOperationId; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CSharpClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CSharpClientCodegen.java index e087bfda2a25..a37e0117d717 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CSharpClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CSharpClientCodegen.java @@ -1316,18 +1316,18 @@ public String toEnumVarName(String value, String datatype) { datatype.startsWith("long") || datatype.startsWith("ulong") || datatype.startsWith("double") || datatype.startsWith("float")) { String varName = "NUMBER_" + value; - varName = varName.replaceAll("-", "MINUS_"); - varName = varName.replaceAll("\\+", "PLUS_"); - varName = varName.replaceAll("\\.", "_DOT_"); + varName = MINUS.matcher(varName).replaceAll("MINUS_"); + varName = PLUS.matcher(varName).replaceAll("PLUS_"); + varName = DOT.matcher(varName).replaceAll("_DOT_"); return varName; } // string - String var = value.replaceAll(" ", "_"); + String var = value.replace(" ", "_"); var = camelize(var); - var = var.replaceAll("\\W+", ""); + var = NON_WORD_PLUS.matcher(var).replaceAll(""); - if (var.matches("\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(var).matches()) { return "_" + var; } else { return var; @@ -1350,14 +1350,14 @@ public String toVarName(String name) { name = sanitizeName(name); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } name = getNameUsingModelPropertyNaming(name); // for reserved word or word starting with number, append _ - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CSharpReducedClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CSharpReducedClientCodegen.java index 99ba87ce923e..72663fbce843 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CSharpReducedClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CSharpReducedClientCodegen.java @@ -840,18 +840,18 @@ public String toEnumVarName(String value, String datatype) { 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_"); + varName = MINUS.matcher(varName).replaceAll("MINUS_"); + varName = PLUS.matcher(varName).replaceAll("PLUS_"); + varName = DOT.matcher(varName).replaceAll("_DOT_"); return varName; } // string - String var = value.replaceAll(" ", "_"); + String var = value.replace(" ", "_"); var = camelize(var); - var = var.replaceAll("\\W+", ""); + var = NON_WORD_PLUS.matcher(var).replaceAll(""); - if (var.matches("\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(var).matches()) { return "_" + var; } else { return var; @@ -874,14 +874,14 @@ public String toVarName(String name) { name = sanitizeName(name); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } name = getNameUsingModelPropertyNaming(name); // for reserved word or word starting with number, append _ - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ClojureClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ClojureClientCodegen.java index 08c866a64ef7..4b9de965cfda 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ClojureClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ClojureClientCodegen.java @@ -37,6 +37,7 @@ import java.io.File; import java.util.*; +import java.util.regex.Pattern; import static org.openapitools.codegen.utils.StringUtils.dashize; import static org.openapitools.codegen.utils.StringUtils.underscore; @@ -63,6 +64,9 @@ public class ClojureClientCodegen extends DefaultCodegen implements CodegenConfi protected String sourceFolder = "src"; + private static final Pattern NON_ALPHA_UNDERSCORE_PLUS = Pattern.compile("[^a-zA-Z_]+"); + private static final Pattern NON_ALPHANUMERIC_UNDERSCORE_HYPHEN_PLUS = Pattern.compile("[^a-zA-Z0-9_-]+"); + public ClojureClientCodegen() { super(); @@ -306,7 +310,7 @@ public void preprocessOpenAPI(OpenAPI openAPI) { @Override public String sanitizeTag(String tag) { - return tag.replaceAll("[^a-zA-Z_]+", "_"); + return NON_ALPHA_UNDERSCORE_PLUS.matcher(tag).replaceAll("_"); } @Override @@ -359,7 +363,7 @@ public String toVarName(String name) { return nameMapping.get(name); } - name = name.replaceAll("[^a-zA-Z0-9_-]+", ""); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + name = NON_ALPHANUMERIC_UNDERSCORE_HYPHEN_PLUS.matcher(name).replaceAll(""); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. return name; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppHttplibServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppHttplibServerCodegen.java index 1056c8b30eb7..7c9a5fe0e92d 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppHttplibServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppHttplibServerCodegen.java @@ -79,6 +79,17 @@ public class CppHttplibServerCodegen extends AbstractCppCodegen { private final Logger LOGGER = LoggerFactory.getLogger(CppHttplibServerCodegen.class); + /** Matches a string that consists entirely of uppercase ASCII letters. */ + private static final java.util.regex.Pattern CPP_ALL_UPPER = java.util.regex.Pattern.compile("[A-Z]+"); + /** Matches a camelCase boundary for inserting a space separator. */ + private static final java.util.regex.Pattern CPP_CAMEL_BOUNDARY = java.util.regex.Pattern.compile("([a-z])([A-Z])"); + /** Matches the last digit-only part of a name after an underscore (e.g. {@code _2} in {@code method_2}). */ + private static final java.util.regex.Pattern CPP_TRAILING_DIGIT_PART = java.util.regex.Pattern.compile("\\d+"); + /** Matches a split point at a camelCase boundary OR any non-alphanumeric character. */ + private static final java.util.regex.Pattern CPP_CAMEL_SPLIT = java.util.regex.Pattern.compile("(?<=[a-z0-9])(?=[A-Z])|[^a-zA-Z0-9]"); + /** Matches one or more consecutive non-word-and-non-hyphen separators (spaces, underscores, etc.) in a name. */ + private static final java.util.regex.Pattern CPP_WORD_SEPARATORS = java.util.regex.Pattern.compile("[_\\-\\s]+"); + @Override public void preprocessOpenAPI(OpenAPI openAPI) { super.preprocessOpenAPI(openAPI); @@ -1315,9 +1326,9 @@ private void processModelVariable(CodegenProperty var, CodegenModel model) { } } else { // Explicit default value from schema - use qualified enum name - String defaultVal = var.defaultValue.replaceAll("\"", ""); + String defaultVal = var.defaultValue.replace("\"", ""); // Convert numeric enum values (e.g., "100" -> "_100") - if (defaultVal.matches("^[0-9]+$")) { + if (DIGITS_ONLY.matcher(defaultVal).matches()) { defaultVal = "_" + defaultVal; } // Convert to UPPERCASE to match enum definition @@ -1435,7 +1446,7 @@ public String toModelName(String name) { // Try to extract a number suffix for uniqueness if (name.contains("_")) { String[] parts = name.split("_"); - if (parts.length > 1 && parts[parts.length - 1].matches("\\d+")) { + if (parts.length > 1 && CPP_TRAILING_DIGIT_PART.matcher(parts[parts.length - 1]).matches()) { return "InlineModel" + parts[parts.length - 1]; } } @@ -1828,7 +1839,7 @@ private Map stripPathFromClassName(String path) { path = path.replace("_error", "").replace("_Error", "").replace("error_", "").replace("Error_", ""); } // Remove leading underscores - String clean = path.replaceAll("^_+", ""); + String clean = MULTI_LEADING_UNDERSCORES.matcher(path).replaceAll(""); // If the path is now empty or whitespace only, return default if (clean.isEmpty() || clean.trim().isEmpty()) { @@ -1838,7 +1849,7 @@ private Map stripPathFromClassName(String path) { } // Split by "/", "_", "-", " ", camelCase boundary - String[] segments = clean.split("(?<=[a-z0-9])(?=[A-Z])|[^a-zA-Z0-9]"); + String[] segments = CPP_CAMEL_SPLIT.split(clean); List parts = new ArrayList<>(); for (String seg : segments) { if (seg != null && !seg.trim().isEmpty()) { @@ -1940,9 +1951,9 @@ public String toPascalCase(String name) { } // First, insert spaces before uppercase letters to split camelCase - String normalized = name.replaceAll("([a-z])([A-Z])", "$1 $2"); + String normalized = CPP_CAMEL_BOUNDARY.matcher(name).replaceAll("$1 $2"); // Split on delimiters (hyphens, underscores, spaces) - String[] parts = normalized.split("[_\\-\\s]+"); + String[] parts = CPP_WORD_SEPARATORS.split(normalized); StringBuilder result = new StringBuilder(); for (String part : parts) { if (part != null && !part.isEmpty()) { @@ -2020,7 +2031,7 @@ public String camelize(String name, boolean lowercaseFirstLetter) { } // If the result is the same as input and input is all uppercase, - if (result.equals(name) && name.equals(name.toUpperCase(Locale.ROOT)) && name.matches("[A-Z]+")) { + if (result.equals(name) && name.equals(name.toUpperCase(Locale.ROOT)) && CPP_ALL_UPPER.matcher(name).matches()) { // Handle all-caps words manually result = name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1).toLowerCase(Locale.ROOT); if (lowercaseFirstLetter) { @@ -2041,7 +2052,7 @@ public String camelize(String name, boolean lowercaseFirstLetter) { public String toHandlerFunctionName(String httpMethod, String path, Boolean prefix) { String method = toPascalCase(httpMethod); String className = stripPathFromClassName(path).get("className"); - className = className.replaceAll("[^A-Za-z0-9]", ""); + className = NON_ALPHANUMERIC_CHAR.matcher(className).replaceAll(""); if (prefix) { return "handle" + method + "For" + toPascalCase(className); } else { @@ -2057,7 +2068,7 @@ public String toHandlerFunctionName(String httpMethod, String path, Boolean pref */ public String toHandlerFunctionRequest(String path, String method) { String className = stripPathFromClassName(path).get("className"); - className = className.replaceAll("[^A-Za-z0-9]", ""); + className = NON_ALPHANUMERIC_CHAR.matcher(className).replaceAll(""); return toPascalCase(className + toPascalCase(method) + "Request"); } @@ -2070,7 +2081,7 @@ public String toHandlerFunctionRequest(String path, String method) { */ public String toHandlerFunctionResponse(String path, String method) { String className = stripPathFromClassName(path).get("className"); - className = className.replaceAll("[^A-Za-z0-9]", ""); + className = NON_ALPHANUMERIC_CHAR.matcher(className).replaceAll(""); return toPascalCase(className + toPascalCase(method) + "Response"); } @@ -2374,7 +2385,7 @@ private void convertNumericEnumValues(CodegenProperty property) { String enumValStr = enumVal != null ? enumVal.toString() : ""; String convertedVal = enumValStr; // Convert numeric values to have underscore prefix - if (enumValStr.matches("^[0-9]+$")) { + if (DIGITS_ONLY.matcher(enumValStr).matches()) { convertedVal = "_" + enumValStr; } // Convert to UPPERCASE for C++ enum standards (clang style) @@ -2457,7 +2468,7 @@ private void setEnumVendorExtensions(Object varObj, CodegenModel model) { for (String enumVal : enumValues) { // Convert numeric values to words or keep string values as-is String convertedVal = enumVal; - if (enumVal.matches("^[0-9]+$")) { + if (DIGITS_ONLY.matcher(enumVal).matches()) { // Convert number to enum name convertedVal = "_" + enumVal; // Prefix with underscore for numeric values } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppOatppClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppOatppClientCodegen.java index eec28ac05de1..20f8b9da773e 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppOatppClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppOatppClientCodegen.java @@ -155,10 +155,10 @@ public void processOpts() { reservedWordPrefix = (String) additionalProperties.get(RESERVED_WORD_PREFIX_OPTION); } - additionalProperties.put("modelNamespaceDeclarations", modelPackage.split("\\.")); - additionalProperties.put("modelNamespace", modelPackage.replaceAll("\\.", "::")); - additionalProperties.put("apiNamespaceDeclarations", apiPackage.split("\\.")); - additionalProperties.put("apiNamespace", apiPackage.replaceAll("\\.", "::")); + additionalProperties.put("modelNamespaceDeclarations", DOT.split(modelPackage)); + additionalProperties.put("modelNamespace", DOT.matcher(modelPackage).replaceAll("::")); + additionalProperties.put("apiNamespaceDeclarations", DOT.split(apiPackage)); + additionalProperties.put("apiNamespace", DOT.matcher(apiPackage).replaceAll("::")); additionalProperties.put(RESERVED_WORD_PREFIX_OPTION, reservedWordPrefix); if (additionalProperties.containsKey(OPTIONAL_EXTERNAL_LIB)) { @@ -219,7 +219,7 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation } } - String pathForOatpp = path.replaceAll("\\{(.*?)}", "{$1}"); + String pathForOatpp = PATH_PARAMETER.matcher(path).replaceAll("{$1}"); op.vendorExtensions.put("x-codegen-oatpp-path", pathForOatpp); return op; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppOatppServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppOatppServerCodegen.java index d4d1a33fb7d1..48921ab8ccc3 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppOatppServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppOatppServerCodegen.java @@ -158,10 +158,10 @@ public void processOpts() { reservedWordPrefix = (String) additionalProperties.get(RESERVED_WORD_PREFIX_OPTION); } - additionalProperties.put("modelNamespaceDeclarations", modelPackage.split("\\.")); - additionalProperties.put("modelNamespace", modelPackage.replaceAll("\\.", "::")); - additionalProperties.put("apiNamespaceDeclarations", apiPackage.split("\\.")); - additionalProperties.put("apiNamespace", apiPackage.replaceAll("\\.", "::")); + additionalProperties.put("modelNamespaceDeclarations", DOT.split(modelPackage)); + additionalProperties.put("modelNamespace", DOT.matcher(modelPackage).replaceAll("::")); + additionalProperties.put("apiNamespaceDeclarations", DOT.split(apiPackage)); + additionalProperties.put("apiNamespace", DOT.matcher(apiPackage).replaceAll("::")); additionalProperties.put(RESERVED_WORD_PREFIX_OPTION, reservedWordPrefix); if (additionalProperties.containsKey(OPTIONAL_EXTERNAL_LIB)) { @@ -222,7 +222,7 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation } } - String pathForOatpp = path.replaceAll("\\{(.*?)}", "{$1}"); + String pathForOatpp = PATH_PARAMETER.matcher(path).replaceAll("{$1}"); op.vendorExtensions.put("x-codegen-oatpp-path", pathForOatpp); return op; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppPistacheServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppPistacheServerCodegen.java index b740e50db0a9..1adf24d01906 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppPistacheServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppPistacheServerCodegen.java @@ -213,12 +213,12 @@ public void processOpts() { reservedWordPrefix = (String) additionalProperties.get(RESERVED_WORD_PREFIX_OPTION); } - additionalProperties.put("modelNamespaceDeclarations", modelPackage.split("\\.")); - additionalProperties.put("modelNamespace", modelPackage.replaceAll("\\.", "::")); - additionalProperties.put("apiNamespaceDeclarations", apiPackage.split("\\.")); - additionalProperties.put("apiNamespace", apiPackage.replaceAll("\\.", "::")); - additionalProperties.put("helpersNamespaceDeclarations", helpersPackage.split("\\.")); - additionalProperties.put("helpersNamespace", helpersPackage.replaceAll("\\.", "::")); + additionalProperties.put("modelNamespaceDeclarations", DOT.split(modelPackage)); + additionalProperties.put("modelNamespace", DOT.matcher(modelPackage).replaceAll("::")); + additionalProperties.put("apiNamespaceDeclarations", DOT.split(apiPackage)); + additionalProperties.put("apiNamespace", DOT.matcher(apiPackage).replaceAll("::")); + additionalProperties.put("helpersNamespaceDeclarations", DOT.split(helpersPackage)); + additionalProperties.put("helpersNamespace", DOT.matcher(helpersPackage).replaceAll("::")); additionalProperties.put(RESERVED_WORD_PREFIX_OPTION, reservedWordPrefix); if (additionalProperties.containsKey(OPTIONAL_EXTERNAL_LIB)) { @@ -309,7 +309,7 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation } } - String pathForPistache = path.replaceAll("\\{(.*?)}", ":$1"); + String pathForPistache = PATH_PARAMETER.matcher(path).replaceAll(":$1"); op.vendorExtensions.put("x-codegen-pistache-path", pathForPistache); return op; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppQtAbstractCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppQtAbstractCodegen.java index 82f87d6b8008..758e66fe19fe 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppQtAbstractCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppQtAbstractCodegen.java @@ -304,7 +304,7 @@ public String toVarName(String name) { varName = sanitizeName(name); // if it's all upper case, convert to lower case - if (varName.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(varName).matches()) { varName = varName.toLowerCase(Locale.ROOT); } @@ -313,7 +313,7 @@ public String toVarName(String name) { varName = org.openapitools.codegen.utils.StringUtils.underscore(varName); // for reserved word or word starting with number, append _ - if (isReservedWord(varName) || varName.matches("^\\d.*")) { + if (isReservedWord(varName) || STARTS_WITH_DIGIT.matcher(varName).matches()) { varName = escapeReservedWord(varName); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestSdkClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestSdkClientCodegen.java index 5a8e523cf513..f3c449e36f5b 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestSdkClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestSdkClientCodegen.java @@ -207,12 +207,12 @@ public void processOpts() { } additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName); - additionalProperties.put("modelNamespaceDeclarations", modelPackage.split("\\.")); - additionalProperties.put("modelNamespace", modelPackage.replaceAll("\\.", "::")); - additionalProperties.put("modelHeaderGuardPrefix", modelPackage.replaceAll("\\.", "_").toUpperCase(Locale.ROOT)); - additionalProperties.put("apiNamespaceDeclarations", apiPackage.split("\\.")); - additionalProperties.put("apiNamespace", apiPackage.replaceAll("\\.", "::")); - additionalProperties.put("apiHeaderGuardPrefix", apiPackage.replaceAll("\\.", "_").toUpperCase(Locale.ROOT)); + additionalProperties.put("modelNamespaceDeclarations", DOT.split(modelPackage)); + additionalProperties.put("modelNamespace", DOT.matcher(modelPackage).replaceAll("::")); + additionalProperties.put("modelHeaderGuardPrefix", DOT.matcher(modelPackage).replaceAll("_").toUpperCase(Locale.ROOT)); + additionalProperties.put("apiNamespaceDeclarations", DOT.split(apiPackage)); + additionalProperties.put("apiNamespace", DOT.matcher(apiPackage).replaceAll("::")); + additionalProperties.put("apiHeaderGuardPrefix", DOT.matcher(apiPackage).replaceAll("_").toUpperCase(Locale.ROOT)); additionalProperties.put("declspec", declspec); additionalProperties.put("defaultInclude", defaultInclude); additionalProperties.put(RESERVED_WORD_PREFIX_OPTION, reservedWordPrefix); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestbedServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestbedServerCodegen.java index bf2054e472c7..a7d3994c7304 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestbedServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestbedServerCodegen.java @@ -231,10 +231,10 @@ public void processOpts() { reservedWordPrefix = additionalProperties.get(RESERVED_WORD_PREFIX_OPTION).toString(); } - additionalProperties.put("modelNamespaceDeclarations", modelPackage.split("\\.")); - additionalProperties.put("modelNamespace", modelPackage.replaceAll("\\.", "::")); - additionalProperties.put("apiNamespaceDeclarations", apiPackage.split("\\.")); - additionalProperties.put("apiNamespace", apiPackage.replaceAll("\\.", "::")); + additionalProperties.put("modelNamespaceDeclarations", DOT.split(modelPackage)); + additionalProperties.put("modelNamespace", DOT.matcher(modelPackage).replaceAll("::")); + additionalProperties.put("apiNamespaceDeclarations", DOT.split(apiPackage)); + additionalProperties.put("apiNamespace", DOT.matcher(apiPackage).replaceAll("::")); additionalProperties.put("declspec", declspec); additionalProperties.put("defaultInclude", defaultInclude); additionalProperties.put(RESERVED_WORD_PREFIX_OPTION, reservedWordPrefix); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestbedServerDeprecatedCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestbedServerDeprecatedCodegen.java index 603e81e00308..c43fae3ac508 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestbedServerDeprecatedCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppRestbedServerDeprecatedCodegen.java @@ -218,10 +218,10 @@ public void processOpts() { reservedWordPrefix = additionalProperties.get(RESERVED_WORD_PREFIX_OPTION).toString(); } - additionalProperties.put("modelNamespaceDeclarations", modelPackage.split("\\.")); - additionalProperties.put("modelNamespace", modelPackage.replaceAll("\\.", "::")); - additionalProperties.put("apiNamespaceDeclarations", apiPackage.split("\\.")); - additionalProperties.put("apiNamespace", apiPackage.replaceAll("\\.", "::")); + additionalProperties.put("modelNamespaceDeclarations", DOT.split(modelPackage)); + additionalProperties.put("modelNamespace", DOT.matcher(modelPackage).replaceAll("::")); + additionalProperties.put("apiNamespaceDeclarations", DOT.split(apiPackage)); + additionalProperties.put("apiNamespace", DOT.matcher(apiPackage).replaceAll("::")); additionalProperties.put("declspec", declspec); additionalProperties.put("defaultInclude", defaultInclude); additionalProperties.put(RESERVED_WORD_PREFIX_OPTION, reservedWordPrefix); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppTinyClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppTinyClientCodegen.java index 7c55e0fbb24b..8f84f08b493c 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppTinyClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppTinyClientCodegen.java @@ -337,8 +337,8 @@ public String toApiImport(String name) { @Override public String toVarName(String name) { - String paramName = name.replaceAll("[^a-zA-Z0-9_]", ""); - if (name.length() > 0) { + String paramName = NON_WORD_CHAR.matcher(name).replaceAll(""); + if (!name.isEmpty()) { paramName = Character.toLowerCase(paramName.charAt(0)) + paramName.substring(1); } if (isReservedWord(paramName)) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppTizenClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppTizenClientCodegen.java index 9341df65be7d..36c36a87f92b 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppTizenClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppTizenClientCodegen.java @@ -288,8 +288,8 @@ public String toApiFilename(String name) { @Override public String toVarName(String name) { - String paramName = name.replaceAll("[^a-zA-Z0-9_]", ""); - if (name.length() > 0) { + String paramName = NON_WORD_CHAR.matcher(name).replaceAll(""); + if (!name.isEmpty()) { // additionalProperties name is "" so name.length() == 0 paramName = Character.toLowerCase(paramName.charAt(0)) + paramName.substring(1); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppUE4ClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppUE4ClientCodegen.java index 13ab898fa39a..736461fc5797 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppUE4ClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppUE4ClientCodegen.java @@ -516,7 +516,7 @@ public String toVarName(String name) { name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. // if it's all upper case, convert to lower case - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { name = name.toLowerCase(Locale.ROOT); } @@ -529,7 +529,7 @@ public String toVarName(String name) { } // for reserved word or word starting with number, append _ - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java index 29bb9e09ab95..ecc1faeb3c53 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java @@ -407,7 +407,7 @@ public String toModelName(final String name) { } // model name starts with number - if (modelName.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(modelName).matches()) { LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", modelName, camelize("model_" + modelName)); // e.g. 200Response => Model200Response (after camelize) modelName = "model_" + modelName; @@ -450,7 +450,7 @@ public String toApiFilename(final String name) { filename = filename + "_" + apiNameSuffix; } - filename = filename.replaceAll("-", "_"); + filename = MINUS.matcher(filename).replaceAll("_"); // e.g. PhoneNumberApi.cr => phone_number_api.cr return underscore(filename); @@ -494,18 +494,18 @@ public String toEnumVarName(String name, String datatype) { // number if ("Integer".equals(datatype) || "Float".equals(datatype)) { String varName = name; - varName = varName.replaceAll("-", "MINUS_"); - varName = varName.replaceAll("\\+", "PLUS_"); - varName = varName.replaceAll("\\.", "_DOT_"); + varName = MINUS.matcher(varName).replaceAll("MINUS_"); + varName = PLUS.matcher(varName).replaceAll("PLUS_"); + varName = DOT.matcher(varName).replaceAll("_DOT_"); return NUMERIC_ENUM_PREFIX + varName; } // string String enumName = sanitizeName(underscore(name).toUpperCase(Locale.ROOT)); - enumName = enumName.replaceFirst("^_", ""); - enumName = enumName.replaceFirst("_$", ""); + enumName = FIRST_LEADING_UNDERSCORE.matcher(enumName).replaceFirst(""); + enumName = LAST_TRAILING_UNDERSCORE.matcher(enumName).replaceFirst(""); - if (enumName.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(enumName).matches()) { // starts with number return NUMERIC_ENUM_PREFIX + enumName; } else { return enumName; @@ -515,10 +515,10 @@ public String toEnumVarName(String name, String datatype) { @Override public String toEnumName(CodegenProperty property) { String enumName = underscore(toModelName(property.name)).toUpperCase(Locale.ROOT); - enumName = enumName.replaceFirst("^_", ""); - enumName = enumName.replaceFirst("_$", ""); + enumName = FIRST_LEADING_UNDERSCORE.matcher(enumName).replaceFirst(""); + enumName = LAST_TRAILING_UNDERSCORE.matcher(enumName).replaceFirst(""); - if (enumName.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(enumName).matches()) { // starts with number return NUMERIC_ENUM_PREFIX + enumName; } else { return enumName; @@ -548,7 +548,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, underscore(sanitizeName("call_" + operationId))); operationId = "call_" + operationId; } @@ -871,7 +871,7 @@ public String toVarName(final String name) { // sanitize name varName = sanitizeName(name); // if it's all upper case, convert to lower case - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { varName = varName.toLowerCase(Locale.ROOT); } @@ -880,7 +880,7 @@ public String toVarName(final String name) { varName = underscore(varName); // for reserved word or word starting with number, append _ - if (isReservedWord(varName) || varName.matches("^\\d.*")) { + if (isReservedWord(varName) || STARTS_WITH_DIGIT.matcher(varName).matches()) { varName = escapeReservedWord(varName); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/EiffelClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/EiffelClientCodegen.java index af66fcf53e39..b42dc64651f9 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/EiffelClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/EiffelClientCodegen.java @@ -212,15 +212,15 @@ public String toEnumVarName(String value, String datatype) { if ("INTEGER_32".equals(datatype) || "INTEGER_64".equals(datatype) || "REAL_32".equals(datatype) || "REAL_64".equals(datatype)) { String varName = "NUMBER_" + value; - varName = varName.replaceAll("-", "MINUS_"); - varName = varName.replaceAll("\\+", "PLUS_"); - varName = varName.replaceAll("\\.", "_DOT_"); + varName = MINUS.matcher(varName).replaceAll("MINUS_"); + varName = PLUS.matcher(varName).replaceAll("PLUS_"); + varName = DOT.matcher(varName).replaceAll("_DOT_"); return varName; } // string - String var = value.replaceAll("\\W+", "_").toLowerCase(Locale.ROOT); - if (var.matches("\\d.*")) { + String var = NON_WORD_PLUS.matcher(value).replaceAll("_").toLowerCase(Locale.ROOT); + if (STARTS_WITH_DIGIT.matcher(var).matches()) { return "val_" + var; } else if (var.startsWith("_")) { return "val" + var; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ElixirClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ElixirClientCodegen.java index c68ad41f8a08..e6245618f081 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ElixirClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ElixirClientCodegen.java @@ -48,6 +48,7 @@ public class ElixirClientCodegen extends DefaultCodegen { private final Logger LOGGER = LoggerFactory.getLogger(ElixirClientCodegen.class); + private static final Pattern PATH_TEMPLATE_PATTERN = Pattern.compile("\\{([^\\}]+)\\}([^\\{]*)"); private final Pattern simpleAtomPattern = Pattern.compile("\\A(?:(?:[_\\p{Alpha}][_@\\p{Alnum}]*[?!]?)|-|@)\\z"); @Setter protected String packageVersion = "1.0.0"; @@ -348,10 +349,9 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List os = operations.getOperation(); List newOs = new ArrayList<>(); - Pattern pattern = Pattern.compile("\\{([^\\}]+)\\}([^\\{]*)"); for (CodegenOperation o : os) { ArrayList pathTemplateNames = new ArrayList<>(); - Matcher matcher = pattern.matcher(o.path); + Matcher matcher = PATH_TEMPLATE_PATTERN.matcher(o.path); StringBuffer buffer = new StringBuffer(); while (matcher.find()) { String pathTemplateName = matcher.group(1); @@ -453,13 +453,13 @@ public String escapeReservedWord(String name) { String escapedName = name + "_var"; // Trim leading underscores in the event the name is already underscored - escapedName = escapedName.replaceAll("^_+", ""); + escapedName = MULTI_LEADING_UNDERSCORES.matcher(escapedName).replaceAll(""); return escapedName; } private String sourceFolder() { ArrayList underscoredWords = new ArrayList<>(); - for (String word : moduleName.split("\\.")) { + for (String word : DOT.split(moduleName)) { underscoredWords.add(underscore(word)); } return ("lib/" + join("/", underscoredWords)).replace('/', File.separatorChar); @@ -496,7 +496,7 @@ public String toApiName(String name) { @Override public String toApiFilename(String name) { // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); + name = MINUS.matcher(name).replaceAll("_"); // e.g. PetApi.go => pet_api.go return underscore(name); @@ -539,7 +539,7 @@ public String toModelFilename(String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, "model_" + name); name = "model_" + name; // e.g. 200Response => Model200Response (after camelize) @@ -564,7 +564,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, underscore(sanitizeName("call_" + operationId))); operationId = "call_" + operationId; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ElmClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ElmClientCodegen.java index ed17189a1fd3..dd4af3368573 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ElmClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ElmClientCodegen.java @@ -198,7 +198,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { LOGGER.warn(operationId + " (starting with a number) cannot be used as method sname. Renamed to " + camelize("call_" + operationId), true); operationId = camelize("call_" + operationId, LOWERCASE_FIRST_LETTER); } @@ -228,7 +228,7 @@ public String toEnumName(CodegenProperty property) { @Override public String toVarName(String name) { // Replace space with _ (underscore) so camelize works as expected - final String varName = camelize(name.replaceAll(" ", "_").replaceAll("[^a-zA-Z0-9_]", ""), + final String varName = camelize(NON_WORD_CHAR.matcher(name.replace(" ", "_")).replaceAll(""), LOWERCASE_FIRST_LETTER); return isReservedWord(varName) ? escapeReservedWord(name) : varName; } @@ -490,7 +490,7 @@ public String getTypeDeclaration(Schema p) { private static class RemoveWhitespaceLambda implements Mustache.Lambda { @Override public void execute(final Template.Fragment fragment, final Writer writer) throws IOException { - writer.write(fragment.execute().replaceAll("\\s+", " ").trim()); + writer.write(WHITESPACE.matcher(fragment.execute()).replaceAll(" ").trim()); } } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangClientCodegen.java index a596660a9c60..643d34541221 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangClientCodegen.java @@ -44,8 +44,12 @@ public class ErlangClientCodegen extends DefaultCodegen implements CodegenConfig { private final Logger LOGGER = LoggerFactory.getLogger(ErlangClientCodegen.class); - @Setter protected String packageName = "openapi"; - @Setter protected String packageVersion = "1.0.0"; + private static final Pattern PATH_TEMPLATE_PATTERN = Pattern.compile("\\{([^\\}]+)\\}"); + + @Setter + protected String packageName = "openapi"; + @Setter + protected String packageVersion = "1.0.0"; protected String sourceFolder = "src"; @Override @@ -256,7 +260,7 @@ public String toVarName(String name) { } // replace - with _ e.g. created-at => created_at - name = sanitizeName(name.replaceAll("-", "_")); + name = sanitizeName(MINUS.matcher(name).replaceAll("_")); // for reserved word or word starting with number, append _ if (isReservedWord(name)) name = escapeReservedWord(name); @@ -289,24 +293,24 @@ public String toArrayModelParamName(String name) { @Override public String toModelName(String name) { - return this.packageName + "_" + underscore(name.replaceAll("-", "_").replaceAll("\\.", "_")); + return this.packageName + "_" + underscore(DOT.matcher(MINUS.matcher(name).replaceAll("_")).replaceAll("_")); } @Override public String toApiName(String name) { - return this.packageName + "_" + underscore(name.replaceAll("-", "_").replaceAll("\\.", "_")); + return this.packageName + "_" + underscore(DOT.matcher(MINUS.matcher(name).replaceAll("_")).replaceAll("_")); } @Override public String toModelFilename(String name) { - return this.packageName + "_" + underscore(name.replaceAll("\\.", "_")); + return this.packageName + "_" + underscore(DOT.matcher(name).replaceAll("_")); } @Override public String toApiFilename(String name) { // replace - with _ e.g. created-at => created_at // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. - name = name.replaceAll("-", "_").replaceAll("\\.", "_"); + name = DOT.matcher(MINUS.matcher(name).replaceAll("_")).replaceAll("_"); // e.g. PetApi.erl => pet_api.erl return this.packageName + "_" + underscore(name) + "_api"; @@ -316,11 +320,11 @@ public String toApiFilename(String name) { public String toOperationId(String operationId) { // method name cannot use reserved keyword, e.g. if if (isReservedWord(operationId)) { - LOGGER.warn("{} (reserved word) cannot be used as method name. Renamed to {}", operationId, underscore(sanitizeName("call_" + operationId)).replaceAll("\\.", "_")); + LOGGER.warn("{} (reserved word) cannot be used as method name. Renamed to {}", operationId, DOT.matcher(underscore(sanitizeName("call_" + operationId))).replaceAll("_")); operationId = "call_" + operationId; } - return underscore(operationId.replaceAll("\\.", "_")); + return underscore(DOT.matcher(operationId).replaceAll("_")); } @Override @@ -328,7 +332,6 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List os = operations.getOperation(); List newOs = new ArrayList<>(); - Pattern pattern = Pattern.compile("\\{([^\\}]+)\\}"); for (CodegenOperation o : os) { // force http method to lower case o.httpMethod = o.httpMethod.toLowerCase(Locale.ROOT); @@ -338,7 +341,7 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List pathTemplateNames = new ArrayList<>(); - Matcher matcher = pattern.matcher(o.path); + Matcher matcher = PATH_TEMPLATE_PATTERN.matcher(o.path); StringBuffer buffer = new StringBuffer(); while (matcher.find()) { String pathTemplateName = matcher.group(1); @@ -402,9 +405,11 @@ public String escapeUnsafeCharacters(String input) { } class ExtendedCodegenOperation extends CodegenOperation { - @Getter @Setter + @Getter + @Setter private List pathTemplateNames = new ArrayList(); - @Getter @Setter + @Getter + @Setter private String replacedPathName; String arityRequired; String arityOptional; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangProperCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangProperCodegen.java index c9ce46417995..05179e0c20e5 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangProperCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangProperCodegen.java @@ -311,7 +311,7 @@ public String modelFileFolder() { @Override public String toVarName(String name) { // replace - with _ e.g. created-at => created_at - name = sanitizeName(name.replaceAll("-", "_")); + name = sanitizeName(MINUS.matcher(name).replaceAll("_")); // for reserved word or word starting with number, append _ if (isReservedWord(name)) name = escapeReservedWord(name); @@ -340,7 +340,7 @@ public String toArrayModelParamName(String name) { @Override public String toModelName(String name) { - return this.packageName + "_" + underscore(name.replaceAll("-", "_").replaceAll("\\.", "_")); + return this.packageName + "_" + underscore(DOT.matcher(MINUS.matcher(name).replaceAll("_")).replaceAll("_")); } @Override @@ -350,7 +350,7 @@ public String toApiName(String name) { @Override public String toModelFilename(String name) { - return this.packageName + "_" + underscore(name.replaceAll("\\.", "_")); + return this.packageName + "_" + underscore(DOT.matcher(name).replaceAll("_")); } @Override @@ -362,11 +362,11 @@ public String toApiFilename(String name) { public String toOperationId(String operationId) { // method name cannot use reserved keyword, e.g. return if (isReservedWord(operationId)) { - LOGGER.warn("{} (reserved word) cannot be used as method name. Renamed to {}", operationId, underscore(sanitizeName("call_" + operationId)).replaceAll("\\.", "_")); + LOGGER.warn("{} (reserved word) cannot be used as method name. Renamed to {}", operationId, DOT.matcher(underscore(sanitizeName("call_" + operationId))).replaceAll("_")); operationId = "call_" + operationId; } - return underscore(operationId.replaceAll("\\.", "_")); + return underscore(DOT.matcher(operationId).replaceAll("_")); } @Override diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangServerCodegen.java index 4581e6e4509f..b4b246bbf575 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangServerCodegen.java @@ -276,7 +276,7 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List operationList = operations.getOperation(); for (CodegenOperation op : operationList) { if (op.path != null) { - op.path = op.path.replaceAll("\\{(.*?)\\}", ":$1"); + op.path = PATH_PARAMETER.matcher(op.path).replaceAll(":$1"); } } return objs; @@ -293,7 +293,7 @@ protected String toHandlerName(String name) { } protected String toModuleName(String name) { - return this.packageName + "_" + underscore(name.replaceAll("-", "_")); + return this.packageName + "_" + underscore(MINUS.matcher(name).replaceAll("_")); } protected String toSourceFilePath(String name, String extension) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangServerDeprecatedCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangServerDeprecatedCodegen.java index c0e0e60a32c4..2d5ff7aa36dc 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangServerDeprecatedCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangServerDeprecatedCodegen.java @@ -282,7 +282,7 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List operationList = operations.getOperation(); for (CodegenOperation op : operationList) { if (op.path != null) { - op.path = op.path.replaceAll("\\{(.*?)\\}", ":$1"); + op.path = PATH_PARAMETER.matcher(op.path).replaceAll(":$1"); } } return objs; @@ -299,7 +299,7 @@ protected String toHandlerName(String name) { } protected String toModuleName(String name) { - return this.packageName + "_" + underscore(name.replaceAll("-", "_")); + return this.packageName + "_" + underscore(MINUS.matcher(name).replaceAll("_")); } protected String toSourceFilePath(String name, String extension) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GdscriptClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GdscriptClientCodegen.java index e2e7c5dc2004..e95a4e6d2642 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GdscriptClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GdscriptClientCodegen.java @@ -343,9 +343,9 @@ public String escapeReservedWord(String name) { } public String escapeStringLiteral(String input) { - return input - .replace("\"", "\\\"") // escape double quotes - .replaceAll("[\\\\]+$", "") // remove trailing backslash(es) + return TRAILING_BACKSLASHES.matcher(input + .replace("\"", "\\\"")) // escape double quotes + .replaceAll("") // remove trailing backslash(es) // issue: "foo\" will perhaps still wreak havoc ; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoEchoServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoEchoServerCodegen.java index 621494b0c7a5..c3be9c4ddf5f 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoEchoServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoEchoServerCodegen.java @@ -118,7 +118,7 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List operationList = operations.getOperation(); for (CodegenOperation op : operationList) { if (op.path != null) { - op.path = op.path.replaceAll("\\{(.*?)\\}", ":$1"); + op.path = PATH_PARAMETER.matcher(op.path).replaceAll(":$1"); } } return objs; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoGinServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoGinServerCodegen.java index ce8721a251ed..c76e4762fc2b 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoGinServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GoGinServerCodegen.java @@ -133,7 +133,7 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List operationList = operations.getOperation(); for (CodegenOperation op : operationList) { if (op.path != null) { - op.path = op.path.replaceAll("\\{(.*?)\\}", ":$1"); + op.path = PATH_PARAMETER.matcher(op.path).replaceAll(":$1"); } } return objs; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GraphQLNodeJSExpressServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GraphQLNodeJSExpressServerCodegen.java index 7bab192f4d50..2f62187e385f 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GraphQLNodeJSExpressServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/GraphQLNodeJSExpressServerCodegen.java @@ -159,7 +159,7 @@ public String toEnumName(CodegenProperty property) { enumName = enumName.replace("[]", ""); // ENUM starts with a number - if (enumName.matches("\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(enumName).matches()) { return StringUtils.capitalize("_" + enumName) + "Enum"; } else { return StringUtils.capitalize(enumName) + "Enum"; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java index 00d1a75e9673..0ed1e43aef51 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java @@ -32,6 +32,7 @@ import org.openapitools.codegen.model.OperationMap; import org.openapitools.codegen.model.OperationsMap; import org.openapitools.codegen.utils.ModelUtils; +import org.openapitools.codegen.utils.PatternCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,6 +50,12 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenConfig { private final Logger LOGGER = LoggerFactory.getLogger(HaskellHttpClientCodegen.class); + private static final Pattern XPATH_EMPTY_SEGMENT = Pattern.compile(",\"\","); + private static final Pattern XPATH_LEADING_EMPTY = Pattern.compile("\"\","); + private static final Pattern XPATH_TRAILING_EMPTY = Pattern.compile(",\"\""); + private static final Pattern XPATH_LEADING_COMMA = Pattern.compile("^\\[,"); + private static final Pattern XPATH_TRAILING_COMMA = Pattern.compile(",]$"); + // source folder where to write the files protected String sourceFolder = "lib"; @@ -87,8 +94,6 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC // protected String MODEL_IMPORTS = "modelImports"; // protected String MODEL_EXTENSIONS = "modelExtensions"; - private static final Pattern LEADING_UNDERSCORE = Pattern.compile("^_+"); - static final String MEDIA_TYPE = "mediaType"; static final String MIME_NO_CONTENT = "MimeNoContent"; static final String MIME_ANY = "MimeAny"; @@ -1059,14 +1064,14 @@ private void processPathExpr(CodegenOperation op) { if (op.getHasPathParams()) { for (CodegenParameter param : op.allParams) { if (param.isPathParam) { - xPath = xPath.replaceAll("\\{" + param.baseName + "\\}", "\",toPath " + param.paramName + ",\""); + xPath = PatternCache.get("\\{" + param.baseName + "}").matcher(xPath).replaceAll("\",toPath " + param.paramName + ",\""); } } - xPath = xPath.replaceAll(",\"\",", ","); - xPath = xPath.replaceAll("\"\",", ","); - xPath = xPath.replaceAll(",\"\"", ","); - xPath = xPath.replaceAll("^\\[,", "["); - xPath = xPath.replaceAll(",\\]$", "]"); + xPath = XPATH_EMPTY_SEGMENT.matcher(xPath).replaceAll(","); + xPath = XPATH_LEADING_EMPTY.matcher(xPath).replaceAll(","); + xPath = XPATH_TRAILING_EMPTY.matcher(xPath).replaceAll(","); + xPath = XPATH_LEADING_COMMA.matcher(xPath).replaceAll("["); + xPath = XPATH_TRAILING_COMMA.matcher(xPath).replaceAll("]"); } op.vendorExtensions.put(VENDOR_EXTENSION_X_PATH, xPath); } @@ -1159,8 +1164,7 @@ private String getMimeDataType(String mimeType) { } 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; @@ -1192,7 +1196,7 @@ public String toVarName(String name) { public String toVarName(String prefix, String name) { boolean hasPrefix = !StringUtils.isBlank(prefix); - name = underscore(sanitizeName(name.replaceAll("-", "_"))); + name = underscore(sanitizeName(MINUS.matcher(name).replaceAll("_"))); if (name.equals("_")) { name = "underscore"; } @@ -1201,7 +1205,7 @@ public String toVarName(String prefix, String name) { if (hasPrefix) { return prefix + name; } else { - if (name.matches("^\\d.*")) + if (STARTS_WITH_DIGIT.matcher(name).matches()) name = escapeReservedWord(name); if (isReservedWord(name)) name = escapeReservedWord(name); @@ -1266,7 +1270,7 @@ public String escapeIdentifier(String prefix, String name) { if (isReservedWord(name)) { name = prefix + name; } - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { name = prefix + name; // e.g. 200Response => Model200Response (after camelize) } if (languageSpecificPrimitives.contains(name)) { @@ -1429,9 +1433,9 @@ public String toEnumVarName(String value, String datatype) { // number if (num.contains(datatype.toLowerCase(Locale.ROOT))) { String varName = "Num" + value; - varName = varName.replaceAll("-", "Minus_"); - varName = varName.replaceAll("\\+", "Plus_"); - varName = varName.replaceAll("\\.", "_Dot_"); + varName = MINUS.matcher(varName).replaceAll("Minus_"); + varName = PLUS.matcher(varName).replaceAll("Plus_"); + varName = DOT.matcher(varName).replaceAll("_Dot_"); return "'" + StringUtils.capitalize(sanitizeName(varName)); } @@ -1462,10 +1466,10 @@ public String escapeText(String input) { // outer unescape to retain the original multi-byte characters // finally escalate characters avoiding code injection return escapeUnsafeCharacters( - StringEscapeUtils.unescapeJava( + CONTROL_WHITESPACE.matcher(StringEscapeUtils.unescapeJava( StringEscapeUtils.escapeJava(input) - .replace("\\/", "/")) - .replaceAll("[\\t\\n\\r]", " ") + .replace("\\/", "/"))) + .replaceAll(" ") .replace("\\", "\\\\") .replace("\"", "\\\"")); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellServantCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellServantCodegen.java index 06703ff40856..383575bec51a 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellServantCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellServantCodegen.java @@ -42,7 +42,6 @@ public class HaskellServantCodegen extends DefaultCodegen implements CodegenConf // source folder where to write the files protected String sourceFolder = "src"; protected String apiVersion = "0.0.1"; - private static final Pattern LEADING_UNDERSCORE = Pattern.compile("^_+"); public static final String PROP_SERVE_STATIC = "serveStatic"; public static final String PROP_SERVE_STATIC_DESC = "serve will serve files from the directory 'static'."; @@ -598,7 +597,7 @@ public CodegenOperation fromOperation(String resourcePath, String httpMethod, Op // Because headers is a Map multiple Set-Cookie headers are currently not possible. If someone // uses the workaround with null bytes, remove them and add add each header to the list: // https://github.com/OAI/OpenAPI-Specification/issues/1237#issuecomment-906603675 - String headerName = h.baseName.replaceAll("\0", ""); + String headerName = h.baseName.replace("\0", ""); String headerType = h.dataType; headerContents.add("Header \"" + headerName + "\" " + headerType); } @@ -609,7 +608,7 @@ public CodegenOperation fromOperation(String resourcePath, String httpMethod, Op String code = "200"; for (CodegenResponse r : op.responses) { - if (r.code.matches("2[0-9][0-9]")) { + if (HTTP_2XX_CODE.matcher(r.code).matches()) { code = r.code; break; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellYesodServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellYesodServerCodegen.java index 38e1ba948657..5809707c5299 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellYesodServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellYesodServerCodegen.java @@ -45,8 +45,6 @@ public class HaskellYesodServerCodegen extends DefaultCodegen implements Codegen public static final String PROJECT_NAME = "projectName"; public static final String API_MODULE_NAME = "apiModuleName"; - private static final Pattern LEADING_UNDERSCORE = Pattern.compile("^_+"); - private final Logger LOGGER = LoggerFactory.getLogger(HaskellYesodServerCodegen.class); @Getter @Setter diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JMeterClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JMeterClientCodegen.java index d005f54111b6..55c62a97823d 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JMeterClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JMeterClientCodegen.java @@ -154,7 +154,7 @@ public void preprocessOpenAPI(OpenAPI openAPI) { PathItem path = openAPIGetPathsEntry.getValue(); if (path.readOperations() != null) { for (Operation operation : path.readOperations()) { - String pathWithDollars = pathname.replaceAll("\\{", "\\$\\{"); + String pathWithDollars = LEFT_CURLY_BRACE.matcher(pathname).replaceAll("\\${"); operation.addExtension("x-path", pathWithDollars); } } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaClientCodegen.java index b2faed874176..39e72aac1efb 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaClientCodegen.java @@ -59,6 +59,9 @@ public class JavaClientCodegen extends AbstractJavaCodegen private final Logger LOGGER = LoggerFactory.getLogger(JavaClientCodegen.class); + /** Matches a Feign-style custom HTTP method suffix, e.g. {@code /path:customMethod}. */ + private static final Pattern FEIGN_CUSTOM_METHOD = Pattern.compile("^(.*):([^:]*)$"); + public static final String USE_RX_JAVA2 = "useRxJava2"; public static final String USE_RX_JAVA3 = "useRxJava3"; public static final String DO_NOT_USE_RX = "doNotUseRx"; @@ -942,13 +945,12 @@ public int compare(CodegenParameter one, CodegenParameter another) { if (isLibrary(FEIGN) || isLibrary(FEIGN_HC5)) { OperationMap operations = objs.getOperations(); List operationList = operations.getOperation(); - Pattern methodPattern = Pattern.compile("^(.*):([^:]*)$"); for (CodegenOperation op : operationList) { String path = op.path; String method = ""; // if a custom method is found at the end of the path, cut it off for later - Matcher m = methodPattern.matcher(path); + Matcher m = FEIGN_CUSTOM_METHOD.matcher(path); if (m.find()) { path = m.group(1); method = m.group(2); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaDubboServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaDubboServerCodegen.java index 5d28f7b0b554..8be3b7da5f9d 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaDubboServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaDubboServerCodegen.java @@ -401,8 +401,7 @@ public void processOpts() { if (isUserTitle) { String titleName = (String) additionalProperties.get(TITLE); - mainClassName = (camelize(titleName.trim(), CamelizeOption.UPPERCASE_FIRST_CHAR) + "Application").replaceAll("\\s+", ""); - ; + mainClassName = WHITESPACE.matcher(camelize(titleName.trim(), CamelizeOption.UPPERCASE_FIRST_CHAR) + "Application").replaceAll(""); } else { mainClassName = "OpenApiGeneratorApplication"; } @@ -455,7 +454,7 @@ private boolean isDubbo33OrHigher(String version) { try { String cleanVersion = version.split("-")[0]; - String[] parts = cleanVersion.split("\\."); + String[] parts = DOT.split(cleanVersion); if (parts.length >= 2) { int major = Integer.parseInt(parts[0]); int minor = Integer.parseInt(parts[1]); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaHelidonCommonCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaHelidonCommonCodegen.java index 3ca11c46b991..98359063a074 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaHelidonCommonCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaHelidonCommonCodegen.java @@ -62,6 +62,9 @@ public abstract class JavaHelidonCommonCodegen extends AbstractJavaCodegen implements BeanValidationFeatures, PerformBeanValidationFeatures { + private static final Pattern STATUS_PATTERN = Pattern.compile( + "public static final Status (\\w+)\\s*=\\s*new\\s*Status\\((\\d+)", Pattern.MULTILINE); + static final String HELIDON_MP = "mp"; static final String HELIDON_SE = "se"; @@ -488,10 +491,8 @@ private HashMap loadKnownHttpStatusMap() { if (is == null) { throw new RuntimeException("Unable to locate /java-helidon/common/Status.java to discover known HTTP statuses"); } - Pattern statusPattern = Pattern.compile("public static final Status (\\w+)\\s*=\\s*new\\s*Status\\((\\d+)", - Pattern.MULTILINE); return new Scanner(is, StandardCharsets.UTF_8) - .findAll(statusPattern) + .findAll(STATUS_PATTERN) .collect(HashMap::new, (map, match) -> map.put(match.group(2), match.group(1)), Map::putAll); @@ -937,10 +938,10 @@ private static List localDefaultVersions() throws IOException { return Files.readAllLines(versionsFile.toPath()); } - private static List extractVersions(String xmlContent) { - Pattern versionPattern = Pattern.compile("]*>([^>]+)"); + private static final Pattern VERSION_PATTERN = Pattern.compile("]*>([^>]+)"); - Matcher matcher = versionPattern.matcher(xmlContent); + private static List extractVersions(String xmlContent) { + Matcher matcher = VERSION_PATTERN.matcher(xmlContent); List result = new ArrayList<>(); while (matcher.find()) { result.add(matcher.group(1)); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaInflectorServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaInflectorServerCodegen.java index ae73860f8c2a..7bea84bcc4f2 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaInflectorServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaInflectorServerCodegen.java @@ -238,7 +238,7 @@ public String toApiName(String name) { if (name.length() == 0) { return "DefaultController"; } - name = name.replaceAll("[^a-zA-Z0-9]+", "_"); + name = NON_ALPHANUMERIC.matcher(name).replaceAll("_"); return camelize(name) + "Controller"; } } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaMicronautAbstractCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaMicronautAbstractCodegen.java index 0ff8db956a42..069ed20ca45a 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaMicronautAbstractCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaMicronautAbstractCodegen.java @@ -403,17 +403,17 @@ public void processOpts() { @Override public String apiTestFileFolder() { if (testTool.equals(OPT_TEST_SPOCK)) { - return getOutputDir() + "/src/test/groovy/" + apiPackage().replaceAll("\\.", "/"); + return getOutputDir() + "/src/test/groovy/" + DOT.matcher(apiPackage()).replaceAll("/"); } - return getOutputDir() + "/src/test/java/" + apiPackage().replaceAll("\\.", "/"); + return getOutputDir() + "/src/test/java/" + DOT.matcher(apiPackage()).replaceAll("/"); } @Override public String modelTestFileFolder() { if (testTool.equals(OPT_TEST_SPOCK)) { - return getOutputDir() + "/src/test/groovy/" + modelPackage().replaceAll("\\.", "/"); + return getOutputDir() + "/src/test/groovy/" + DOT.matcher(modelPackage()).replaceAll("/"); } - return getOutputDir() + "/src/test/java/" + modelPackage().replaceAll("\\.", "/"); + return getOutputDir() + "/src/test/java/" + DOT.matcher(modelPackage()).replaceAll("/"); } @Override @@ -694,6 +694,7 @@ public String escapeTextGroovy(String text) { if (text == null) { return null; } + //TODO: this seems like a bug, since it is basically no-op currently. Probably .replace instead of .replaceAll is wanted here? return escapeText(text).replaceAll("'", "\\'"); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaPlayFrameworkCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaPlayFrameworkCodegen.java index 9f2743be01de..86b227ca17f6 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaPlayFrameworkCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaPlayFrameworkCodegen.java @@ -33,13 +33,13 @@ import java.io.File; import java.util.*; import java.util.regex.Matcher; -import java.util.regex.Pattern; import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER; import static org.openapitools.codegen.utils.StringUtils.camelize; public class JavaPlayFrameworkCodegen extends AbstractJavaCodegen implements BeanValidationFeatures { private final Logger LOGGER = LoggerFactory.getLogger(JavaPlayFrameworkCodegen.class); + public static final String TITLE = "title"; public static final String CONFIG_PACKAGE = "configPackage"; public static final String BASE_PACKAGE = "basePackage"; @@ -245,8 +245,7 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List en String operationId = entry.getValue().getOperationId(); return (operationId != null) ? operationId : entry.getKey().name() - + pathname.replaceAll("-", "_").replaceAll("/", "_").replaceAll("[{}]", ""); + + MINUS.matcher(pathname).replaceAll("_").replaceAll("/", "_").replaceAll("[{}]", ""); } protected String extractPortFromHost(String host) { @@ -302,17 +305,15 @@ protected String extractPortFromHost(String host) { private String camelizePath(String path) { String word = path; - Pattern pattern = Pattern.compile("\\{([^/]*)\\}"); - Matcher matcher = pattern.matcher(word); + Matcher matcher = PATH_VARIABLE_PATTERN.matcher(word); while (matcher.find()) { word = matcher.replaceFirst(":" + matcher.group(1)); - matcher = pattern.matcher(word); + matcher = PATH_VARIABLE_PATTERN.matcher(word); } - pattern = Pattern.compile("(_)(.)"); - matcher = pattern.matcher(word); + matcher = UNDERSCORE_CAPITALIZE_PATTERN.matcher(word); while (matcher.find()) { word = matcher.replaceFirst(matcher.group(2).toUpperCase(Locale.ROOT)); - matcher = pattern.matcher(word); + matcher = UNDERSCORE_CAPITALIZE_PATTERN.matcher(word); } return word; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptApolloClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptApolloClientCodegen.java index 03fe2bf5ba88..118984fab16e 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptApolloClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptApolloClientCodegen.java @@ -48,6 +48,10 @@ public class JavascriptApolloClientCodegen extends DefaultCodegen implements Cod @SuppressWarnings("hiding") private final Logger LOGGER = LoggerFactory.getLogger(JavascriptApolloClientCodegen.class); + /** Matches JS/JsDoc primitive type names for quoting in documentation. */ + private static final java.util.regex.Pattern JS_PRIMITIVE_TYPES = + java.util.regex.Pattern.compile("\\b(Boolean|Integer|Number|String|Date|Blob)\\b"); + public static final String PROJECT_NAME = "projectName"; public static final String MODULE_NAME = "moduleName"; public static final String PROJECT_DESCRIPTION = "projectDescription"; @@ -482,7 +486,7 @@ public String toVarName(String name) { } // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } @@ -491,7 +495,7 @@ public String toVarName(String name) { name = getNameUsingModelPropertyNaming(name); // for reserved word or word starting with number, append _ - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } @@ -533,7 +537,7 @@ public String toModelName(String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { String modelName = "Model" + name; // e.g. 200Response => Model200Response (after camelize) LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, modelName); @@ -782,7 +786,7 @@ protected String setPropertyExampleValue(CodegenProperty p) { * @return Normalized type */ private String normalizeType(String type) { - return type.replaceAll("\\b(Boolean|Integer|Number|String|Date|Blob)\\b", "'$1'"); + return JS_PRIMITIVE_TYPES.matcher(type).replaceAll("'$1'"); } @Override @@ -820,7 +824,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { String newOperationId = camelize("call_" + operationId, LOWERCASE_FIRST_LETTER); LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, newOperationId); return newOperationId; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptClientCodegen.java index 83c7550546f8..f44aabdb513e 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptClientCodegen.java @@ -97,6 +97,10 @@ public class JavascriptClientCodegen extends DefaultCodegen implements CodegenCo @Setter protected boolean useURLSearchParams = true; + /** Matches JS/JsDoc primitive type names for quoting in documentation. */ + private static final java.util.regex.Pattern JS_PRIMITIVE_TYPES = + java.util.regex.Pattern.compile("\\b(Boolean|Integer|Number|String|Date|Blob)\\b"); + public JavascriptClientCodegen() { super(); @@ -499,7 +503,7 @@ public String toVarName(String name) { } // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } @@ -508,7 +512,7 @@ public String toVarName(String name) { name = getNameUsingModelPropertyNaming(name); // for reserved word or word starting with number, append _ - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } @@ -560,7 +564,7 @@ public String toModelName(String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { String modelName = "Model" + name; // e.g. 200Response => Model200Response (after camelize) LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, modelName); @@ -809,7 +813,7 @@ protected String setPropertyExampleValue(CodegenProperty p) { * @return Normalized type */ private String normalizeType(String type) { - return type.replaceAll("\\b(Boolean|Integer|Number|String|Date|Blob)\\b", "'$1'"); + return JS_PRIMITIVE_TYPES.matcher(type).replaceAll("'$1'"); } @Override @@ -847,7 +851,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { String newOperationId = camelize("call_" + operationId, LOWERCASE_FIRST_LETTER); LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, newOperationId); return newOperationId; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptClosureAngularClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptClosureAngularClientCodegen.java index 3629b1ed4d19..43563ffdc421 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptClosureAngularClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavascriptClosureAngularClientCodegen.java @@ -186,10 +186,10 @@ public String toVarName(String name) { name = sanitizeName(name); // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); + name = MINUS.matcher(name).replaceAll("_"); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) return name; // camelize the variable name @@ -197,7 +197,7 @@ public String toVarName(String name) { name = camelize(name, LOWERCASE_FIRST_LETTER); // for reserved word or word starting with number, append _ - if (isReservedWord(name) || name.matches("^\\d.*")) + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) name = escapeReservedWord(name); return name; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JetbrainsHttpClientClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JetbrainsHttpClientClientCodegen.java index 5f6711b6d39a..9caedfb94c69 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JetbrainsHttpClientClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JetbrainsHttpClientClientCodegen.java @@ -147,9 +147,7 @@ public static class DoubleMustacheLambda implements Mustache.Lambda { @Override public void execute(Template.Fragment fragment, Writer writer) throws IOException { String text = fragment.execute(); - writer.write(text - .replaceAll("\\{", "{{") - .replaceAll("}", "}}") + writer.write(RIGHT_CURLY_BRACE.matcher(LEFT_CURLY_BRACE.matcher(text).replaceAll("{{")).replaceAll("}}") ); } } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/K6ClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/K6ClientCodegen.java index 0206116364d7..61577427735e 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/K6ClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/K6ClientCodegen.java @@ -778,7 +778,7 @@ public String toVarName(String name) { } // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } @@ -787,7 +787,7 @@ public String toVarName(String name) { name = getNameUsingModelPropertyNaming(name); // for reserved word or word starting with number, append _ - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java index 6730dd636649..053256cd2112 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinSpringServerCodegen.java @@ -56,6 +56,10 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen private final Logger LOGGER = LoggerFactory.getLogger(KotlinSpringServerCodegen.class); + /** Matches {@code kotlin.collections.Mutable} prefix — replaced with its immutable counterpart. */ + private static final java.util.regex.Pattern KOTLIN_MUTABLE_COLLECTION = + java.util.regex.Pattern.compile("kotlin\\.collections\\.Mutable"); + private static final HashSet VARIABLE_RESERVED_WORDS = new HashSet<>(Arrays.asList( "ApiClient", @@ -1363,7 +1367,7 @@ public Map postProcessSupportingFileData(Map obj private String getNonMutableContainerTypeIfNeeded(String type) { if (type != null && type.contains("kotlin.collections.Mutable")) { - return type.replaceAll("kotlin\\.collections\\.Mutable", "kotlin.collections."); + return KOTLIN_MUTABLE_COLLECTION.matcher(type).replaceAll("kotlin.collections."); } return type; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KtormSchemaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KtormSchemaCodegen.java index 4483eec5daee..6113f049f306 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KtormSchemaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KtormSchemaCodegen.java @@ -44,6 +44,8 @@ public class KtormSchemaCodegen extends AbstractKotlinCodegen { private final Logger LOGGER = LoggerFactory.getLogger(KtormSchemaCodegen.class); + private static final Pattern UNSAFE_IDENTIFIER_CHARS = Pattern.compile("[^0-9a-zA-Z$_\\u0080-\\uFFFF]"); + public static final String VENDOR_EXTENSION_SCHEMA = "x-ktorm-schema"; public static final String DEFAULT_DATABASE_NAME = "defaultDatabaseName"; public static final String IMPORT_MODEL_PACKAGE_NAME = "importModelPackageName"; @@ -1061,13 +1063,13 @@ public String toColumnName(String name) { public String toIdentifier(String name, String prefix, String suffix) { String escapedName = escapeQuotedIdentifier(name); // Database, table, and column names cannot end with space characters. - if (escapedName.matches(".*\\s$")) { + if (ENDS_WITH_WHITESPACE.matcher(escapedName).matches()) { LOGGER.warn("Database, table, and column names cannot end with space characters. Check '{}' name", name); - escapedName = escapedName.replaceAll("\\s+$", ""); + escapedName = TRAILING_WHITESPACE.matcher(escapedName).replaceAll(""); } // Identifiers may begin with a digit but unless quoted may not consist solely of digits. - if (escapedName.matches("^\\d+$")) { + if (DIGITS_ONLY.matcher(escapedName).matches()) { LOGGER.warn("Database, table, and column names cannot consist solely of digits. Check '{}' name", name); escapedName = prefix + escapedName + suffix; } @@ -1091,12 +1093,11 @@ public String escapeQuotedIdentifier(String identifier) { // ASCII: [0-9,a-z,A-Z$_] (basic Latin letters, digits 0-9, dollar, underscore) Extended: U+0080 .. U+FFFF // ASCII NUL (U+0000) and supplementary characters (U+10000 and higher) are not permitted in quoted or unquoted identifiers. // This does in fact matches against >\xFFFF and against ^\x0000. works only on Java7+ - Pattern regexp = Pattern.compile("[^0-9a-zA-z$_\\x0080-\\xFFFF]"); - Matcher matcher = regexp.matcher(identifier); + Matcher matcher = UNSAFE_IDENTIFIER_CHARS.matcher(identifier); if (matcher.find()) { LOGGER.warn("Identifier '{}' contains unsafe characters out of [0-9,a-z,A-Z$_] and U+0080..U+FFFF range", identifier); - identifier = identifier.replaceAll("[^0-9a-zA-z$_\\x0080-\\xFFFF]", ""); + identifier = matcher.reset().replaceAll(""); } return identifier; } @@ -1166,7 +1167,7 @@ public String toSrcPath(String packageName) { // Trim prefix file separators from package path String packagePath = StringUtils.removeStart( // Replace period, backslash, forward slash with file separator in package name - packageName.replaceAll("[\\.\\\\/]", Matcher.quoteReplacement("/")), + PACKAGE_SEPARATOR.matcher(packageName).replaceAll(Matcher.quoteReplacement("/")), File.separator ); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/LuaClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/LuaClientCodegen.java index 92c38a91f562..4232dbbfcd42 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/LuaClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/LuaClientCodegen.java @@ -249,10 +249,11 @@ public String toVarName(String name) { } // replace - with _ e.g. created-at => created_at - name = sanitizeName(name.replaceAll("-", "_")); + name = sanitizeName(MINUS.matcher(name).replaceAll("_")); + name = sanitizeName(MINUS.matcher(name).replaceAll("_")); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) return name; // convert variable name to snake case @@ -264,7 +265,7 @@ public String toVarName(String name) { name = escapeReservedWord(name); // for reserved word or word starting with number, append _ - if (name.matches("^\\d.*")) + if (STARTS_WITH_DIGIT.matcher(name).matches()) name = "Var" + name; return name; @@ -309,7 +310,7 @@ public String toModelFilename(String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, "model_" + name); name = "model_" + name; // e.g. 200Response => Model200Response (after camelize) @@ -321,7 +322,7 @@ public String toModelFilename(String name) { @Override public String toApiFilename(String name) { // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + name = MINUS.matcher(name).replaceAll("_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. // e.g. PetApi.lua => pet_api.lua return underscore(name) + "_api"; @@ -545,9 +546,9 @@ public String toEnumVarName(String name, String datatype) { // number if ("int".equals(datatype) || "double".equals(datatype) || "float".equals(datatype)) { String varName = name; - varName = varName.replaceAll("-", "MINUS_"); - varName = varName.replaceAll("\\+", "PLUS_"); - varName = varName.replaceAll("\\.", "_DOT_"); + varName = MINUS.matcher(varName).replaceAll("MINUS_"); + varName = PLUS.matcher(varName).replaceAll("PLUS_"); + varName = DOT.matcher(varName).replaceAll("_DOT_"); return varName; } @@ -558,10 +559,10 @@ public String toEnumVarName(String name, String datatype) { // string String enumName = sanitizeName(underscore(name).toUpperCase(Locale.ROOT)); - enumName = enumName.replaceFirst("^_", ""); - enumName = enumName.replaceFirst("_$", ""); + enumName = FIRST_LEADING_UNDERSCORE.matcher(enumName).replaceFirst(""); + enumName = LAST_TRAILING_UNDERSCORE.matcher(enumName).replaceFirst(""); - if (isReservedWord(enumName) || enumName.matches("\\d.*")) { // reserved word or starts with number + if (isReservedWord(enumName) || STARTS_WITH_DIGIT.matcher(enumName).matches()) { // reserved word or starts with number return escapeReservedWord(enumName); } else { return enumName; @@ -575,7 +576,7 @@ public String toEnumName(CodegenProperty property) { // remove [] for array or map of enum enumName = enumName.replace("[]", ""); - if (enumName.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(enumName).matches()) { // starts with number return "_" + enumName; } else { return enumName; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/MysqlSchemaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/MysqlSchemaCodegen.java index 1da17d653d06..ecb67b92bfc3 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/MysqlSchemaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/MysqlSchemaCodegen.java @@ -37,6 +37,11 @@ public class MysqlSchemaCodegen extends DefaultCodegen implements CodegenConfig { private final Logger LOGGER = LoggerFactory.getLogger(MysqlSchemaCodegen.class); + /** Unsafe characters for unquoted MySQL identifiers: outside [0-9,a-z,A-Z,$,_,U+0080..U+FFFF]. */ + private static final Pattern UNSAFE_UNQUOTED_IDENTIFIER_CHARS = Pattern.compile("[^0-9a-zA-Z$_\\u0080-\\uFFFF]"); + /** Unsafe characters for quoted MySQL identifiers: outside [U+0001..U+007F, U+0080..U+FFFF]. */ + private static final Pattern UNSAFE_QUOTED_IDENTIFIER_CHARS = Pattern.compile("[^\\u0001-\\u007F\\u0080-\\uFFFF]"); + public static final String VENDOR_EXTENSION_MYSQL_SCHEMA = "x-mysql-schema"; public static final String DEFAULT_DATABASE_NAME = "defaultDatabaseName"; public static final String JSON_DATA_TYPE_ENABLED = "jsonDataTypeEnabled"; @@ -1081,13 +1086,13 @@ public String toColumnName(String name) { public String toMysqlIdentifier(String name, String prefix, String suffix) { String escapedName = escapeMysqlQuotedIdentifier(name); // Database, table, and column names cannot end with space characters. - if (escapedName.matches(".*\\s$")) { + if (ENDS_WITH_WHITESPACE.matcher(escapedName).matches()) { LOGGER.warn("Database, table, and column names cannot end with space characters. Check '{}' name", name); - escapedName = escapedName.replaceAll("\\s+$", ""); + escapedName = TRAILING_WHITESPACE.matcher(escapedName).replaceAll(""); } // Identifiers may begin with a digit but unless quoted may not consist solely of digits. - if (escapedName.matches("^\\d+$")) { + if (DIGITS_ONLY.matcher(escapedName).matches()) { LOGGER.warn("Database, table, and column names cannot consist solely of digits. Check '{}' name", name); escapedName = prefix + escapedName + suffix; } @@ -1108,12 +1113,11 @@ public String toMysqlIdentifier(String name, String prefix, String suffix) { */ public String escapeMysqlUnquotedIdentifier(String identifier) { // ASCII: [0-9,a-z,A-Z$_] (basic Latin letters, digits 0-9, dollar, underscore) Extended: U+0080 .. U+FFFF - Pattern regexp = Pattern.compile("[^0-9a-zA-z$_\\u0080-\\uFFFF]"); - Matcher matcher = regexp.matcher(identifier); + Matcher matcher = UNSAFE_UNQUOTED_IDENTIFIER_CHARS.matcher(identifier); if (matcher.find()) { LOGGER.warn("Identifier '{}' contains unsafe characters out of [0-9,a-z,A-Z$_] and U+0080..U+FFFF range", identifier); - identifier = identifier.replaceAll("[^0-9a-zA-z$_\\u0080-\\uFFFF]", ""); + identifier = matcher.reset().replaceAll(""); } // ASCII NUL (U+0000) and supplementary characters (U+10000 and higher) are not permitted in quoted or unquoted identifiers. @@ -1131,12 +1135,11 @@ public String escapeMysqlUnquotedIdentifier(String identifier) { */ public String escapeMysqlQuotedIdentifier(String identifier) { // ASCII: U+0001 .. U+007F Extended: U+0080 .. U+FFFF - Pattern regexp = Pattern.compile("[^\\u0001-\\u007F\\u0080-\\uFFFF]"); - Matcher matcher = regexp.matcher(identifier); + Matcher matcher = UNSAFE_QUOTED_IDENTIFIER_CHARS.matcher(identifier); if (matcher.find()) { LOGGER.warn("Identifier '{}' contains unsafe characters out of U+0001..U+007F and U+0080..U+FFFF range", identifier); - identifier = identifier.replaceAll("[^\\u0001-\\u007F\\u0080-\\uFFFF]", ""); + identifier = matcher.reset().replaceAll(""); } // ASCII NUL (U+0000) and supplementary characters (U+10000 and higher) are not permitted in quoted or unquoted identifiers. @@ -1208,7 +1211,7 @@ public String toSrcPath(String packageName) { // Trim prefix file separators from package path String packagePath = StringUtils.removeStart( // Replace period, backslash, forward slash with file separator in package name - packageName.replaceAll("[\\.\\\\/]", Matcher.quoteReplacement("/")), + PACKAGE_SEPARATOR.matcher(packageName).replaceAll(Matcher.quoteReplacement("/")), File.separator ); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/N4jsClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/N4jsClientCodegen.java index 2448f1dc7e00..dc23d5c61f47 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/N4jsClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/N4jsClientCodegen.java @@ -35,6 +35,7 @@ import java.io.File; import java.util.*; +import java.util.regex.Pattern; import java.util.stream.Collectors; import static org.openapitools.codegen.CodegenConstants.*; @@ -43,7 +44,7 @@ public class N4jsClientCodegen extends DefaultCodegen implements CodegenConfig { public static final String CHECK_REQUIRED_PARAMS_NOT_NULL = "checkRequiredParamsNotNull"; public static final String CHECK_SUPERFLUOUS_BODY_PROPS = "checkSuperfluousBodyProps"; public static final String GENERATE_DEFAULT_API_EXECUTER = "generateDefaultApiExecuter"; - + private static final Pattern COMPOSED_TYPE_DELIMITERS = Pattern.compile("[|&<>]"); final Logger LOGGER = LoggerFactory.getLogger(N4jsClientCodegen.class); final Set forbiddenChars = new HashSet<>(); @@ -300,7 +301,7 @@ protected void addImport(Set importsToBeAddedTo, String type) { } private String[] splitComposedType(String name) { - return name.replace(" ", "").split("[|&<>]"); + return COMPOSED_TYPE_DELIMITERS.split(name.replace(" ", "")); } @Override diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NimClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NimClientCodegen.java index 35f3ff81c2d6..30bddfe35d1f 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NimClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NimClientCodegen.java @@ -45,6 +45,14 @@ public class NimClientCodegen extends DefaultCodegen implements CodegenConfig { public static final String PROJECT_NAME = "projectName"; + /** Matches an underscore, one or more digits, and a trailing underscore; used to strip version markers. */ + private static final Pattern UNDERSCORE_DIGITS_UNDERSCORE = Pattern.compile("_(\\d+)_"); + /** Matches an underscore followed by one or more digits at the end of a string. */ + private static final Pattern UNDERSCORE_DIGITS_END = Pattern.compile("_(\\d+)$"); + /** Matches a valid Nim type name: starts with an uppercase letter and continues with CamelCase segments. */ + private static final Pattern NIM_TYPE_NAME = Pattern.compile( + "^(?:[A-Z]|[a-z]|[\\x80-\\xff])(_?(?:[A-Z]|[a-z]|[\\x80-\\xff]|[0-9]))*$"); + @Setter protected String packageName = "openapiclient"; @Setter protected String packageVersion = "1.0.0"; @@ -416,7 +424,7 @@ public String escapeUnsafeCharacters(String input) { @Override public String toModelImport(String name) { name = normalizeSchemaName(name); - name = name.replaceAll("-", "_"); + name = MINUS.matcher(name).replaceAll("_"); if (importMapping.containsKey(name)) { return sanitizeNimIdentifier("model_" + StringUtils.underscore(importMapping.get(name))); @@ -427,7 +435,7 @@ public String toModelImport(String name) { @Override public String toApiImport(String name) { - name = name.replaceAll("-", "_"); + name = MINUS.matcher(name).replaceAll("_"); if (importMapping.containsKey(name)) { return sanitizeNimIdentifier("api_" + StringUtils.underscore(importMapping.get(name))); } else { @@ -443,12 +451,8 @@ private String normalizeSchemaName(String name) { if (name == null) { return null; } - // Remove underscores around and before digits (HTTP status codes, version numbers, etc.) - // e.g., "GetComments_200_response" -> "GetComments200response" - // e.g., "Config_anyOf_1" -> "ConfiganyOf1" - // This ensures consistent handling whether the name comes with or without underscores - name = name.replaceAll("_(\\d+)_", "$1"); // Underscores on both sides - name = name.replaceAll("_(\\d+)$", "$1"); // Trailing underscore before digits + name = UNDERSCORE_DIGITS_UNDERSCORE.matcher(name).replaceAll("$1"); // Underscores on both sides + name = UNDERSCORE_DIGITS_END.matcher(name).replaceAll("$1"); // Trailing underscore before digits return name; } @@ -505,8 +509,8 @@ private void processComposedSchemaVariants(CodegenModel mdl, List 0 && Character.isUpperCase(sanitizedBase.charAt(0))) { + String sanitizedBase = MULTI_TRAILING_UNDERSCORES.matcher(newVariant.baseName).replaceAll(""); // Remove trailing underscores + if (!sanitizedBase.isEmpty() && Character.isUpperCase(sanitizedBase.charAt(0))) { newVariant.baseName = toModelName(sanitizedBase); } else { newVariant.baseName = sanitizeNimIdentifier(sanitizedBase); @@ -517,7 +521,7 @@ private void processComposedSchemaVariants(CodegenModel mdl, List 0 && Character.isUpperCase(newVariant.dataType.charAt(0))) { + if (!newVariant.dataType.isEmpty() && Character.isUpperCase(newVariant.dataType.charAt(0))) { // This is likely a model type, use toModelName to properly format it newVariant.dataType = toModelName(newVariant.dataType); } else { @@ -571,13 +575,13 @@ public String toModelName(String name) { @Override public String toModelFilename(String name) { name = normalizeSchemaName(name); - name = name.replaceAll("-", "_"); + name = MINUS.matcher(name).replaceAll("_"); return sanitizeNimIdentifier("model_" + StringUtils.underscore(name)); } @Override public String toApiFilename(String name) { - name = name.replaceAll("-", "_"); + name = MINUS.matcher(name).replaceAll("_"); return sanitizeNimIdentifier("api_" + StringUtils.underscore(name)); } @@ -646,7 +650,7 @@ public String getTypeDeclaration(Schema p) { return typeMapping.get(schemaType); } - if (schemaType.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(schemaType).matches()) { // starts with number return "`" + schemaType + "`"; } else { return schemaType; @@ -663,12 +667,12 @@ public String toVarName(String name) { } // numbers are not allowed at the beginning - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { name = "`" + name + "`"; } // if it's all upper case, do nothing - if (name.matches("^[A-Z0-9_]*$")) { + if (ALL_UPPER_UNDERSCORE_DIGITS.matcher(name).matches()) { return name; } @@ -706,7 +710,7 @@ protected boolean needToImport(String type) { public String toEnumName(CodegenProperty property) { String name = StringUtils.camelize(property.name); - if (name.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(name).matches()) { // starts with number return "`" + name + "`"; } else { return name; @@ -715,7 +719,7 @@ public String toEnumName(CodegenProperty property) { private boolean isValidIdentifier(String identifier) { //see https://nim-lang.org/docs/manual.html#lexical-analysis-identifiers-amp-keywords - return identifier.matches("^(?:[A-Z]|[a-z]|[\\x80-\\xff])(_?(?:[A-Z]|[a-z]|[\\x80-\\xff]|[0-9]))*$"); + return NIM_TYPE_NAME.matcher(identifier).matches(); } /** @@ -730,9 +734,9 @@ private String sanitizeNimIdentifier(String name) { return name; } // Remove trailing underscores (Nim identifiers cannot end with underscore) - name = name.replaceAll("_+$", ""); + name = MULTI_TRAILING_UNDERSCORES.matcher(name).replaceAll(""); // Collapse multiple consecutive underscores to single underscore - name = name.replaceAll("_+", "_"); + name = MULTI_UNDERSCORES.matcher(name).replaceAll("_"); return name; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NodeJSExpressServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NodeJSExpressServerCodegen.java index 61931624de0b..34927e1ec01a 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NodeJSExpressServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/NodeJSExpressServerCodegen.java @@ -343,12 +343,11 @@ public void preprocessOpenAPI(OpenAPI openAPI) { if (info.getTitle() != null) { // when info.title is defined, use it for projectName // used in package.json - projectName = info.getTitle() - .replaceAll("[^a-zA-Z0-9]", "-") - .replaceAll("^[-]*", "") - .replaceAll("[-]*$", "") - .replaceAll("[-]{2,}", "-") - .toLowerCase(Locale.ROOT); + projectName = NON_ALPHANUMERIC_CHAR.matcher(info.getTitle()).replaceAll("-"); + projectName = LEADING_HYPHENS.matcher(projectName).replaceAll(""); + projectName = TRAILING_HYPHENS.matcher(projectName).replaceAll(""); + projectName = MULTI_HYPHEN.matcher(projectName).replaceAll("-"); + projectName = projectName.toLowerCase(Locale.ROOT); this.additionalProperties.put("projectName", projectName); } } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/OCamlClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/OCamlClientCodegen.java index c6c8ef3ae866..4e93ba6d44f9 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/OCamlClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/OCamlClientCodegen.java @@ -38,6 +38,7 @@ import java.io.File; import java.util.*; +import java.util.regex.Pattern; import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.capitalize; @@ -51,6 +52,15 @@ public class OCamlClientCodegen extends DefaultCodegen implements CodegenConfig static final String X_MODEL_MODULE = "x-model-module"; + /** Matches a model name that starts with a digit or underscore (invalid OCaml identifier start). */ + private static final Pattern OCAML_INVALID_NAME_START = Pattern.compile("^\\d.*|^_.*"); + /** Matches a string that does NOT start with a letter or underscore. */ + private static final Pattern OCAML_INVALID_IDENTIFIER_START = Pattern.compile("^[^a-zA-Z_]"); + /** Matches a trailing {@code " list"} type suffix in OCaml return types. */ + private static final Pattern OCAML_LIST_SUFFIX = Pattern.compile(" list$"); + /** Matches a comma delimiter used to split enum value strings. */ + private static final Pattern COMMA_DELIMITER = Pattern.compile(","); + @Setter protected String packageName = "openapi"; @Setter @@ -641,7 +651,7 @@ public String modelFileFolder() { @Override public String toVarName(String name) { // replace - with _ e.g. created-at => created_at - name = sanitizeName(name.replaceAll("-", "_")); + name = sanitizeName(MINUS.matcher(name).replaceAll("_")); // snake_case, e.g. PetId => pet_id name = underscore(name); @@ -651,7 +661,7 @@ public String toVarName(String name) { name = escapeReservedWord(name); // for reserved word or word starting with number, append _ - if (name.matches("^\\d.*")) + if (STARTS_WITH_DIGIT.matcher(name).matches()) name = "var_" + name; return name; @@ -689,7 +699,7 @@ public String toModelFilename(String name) { } // model name starts with number or _ - if (name.matches("^\\d.*|^_.*")) { + if (OCAML_INVALID_NAME_START.matcher(name).matches()) { LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, "model_" + name); name = "model_" + name; // e.g. 200Response => Model200Response (after camelize) @@ -701,7 +711,7 @@ public String toModelFilename(String name) { @Override public String toApiFilename(final String name) { // replace - with _ e.g. created-at => created_at - final String _name = name.replaceAll("-", "_"); + final String _name = MINUS.matcher(name).replaceAll("_"); // e.g. PetApi.ml => pet_api.ml return underscore(_name) + "_api"; @@ -791,7 +801,7 @@ public String toOperationId(String operationId) { String sanitizedOperationId = sanitizeName(operationId); // method name cannot use reserved keyword, e.g. return - if (isReservedWord(sanitizedOperationId) || sanitizedOperationId.matches("^[0-9].*")) { + if (isReservedWord(sanitizedOperationId) || STARTS_WITH_DIGIT.matcher(sanitizedOperationId).matches()) { LOGGER.warn("{} (reserved word) cannot be used as method name. Renamed to {}", operationId, underscore("call_" + operationId)); sanitizedOperationId = "call_" + sanitizedOperationId; } @@ -808,7 +818,7 @@ private Map allowableValues(String valueString) { private List> buildEnumValues(String valueString) { List> result = new ArrayList<>(); - for (String v : valueString.split(",")) { + for (String v : COMMA_DELIMITER.split(valueString)) { Map m = new HashMap<>(); String value = v.isEmpty() ? "empty" : v; m.put("name", value); @@ -834,7 +844,7 @@ private String ocamlizeEnumValue(String value) { toEnumValueName(value.isEmpty() ? "empty" : value) .replace(" ", "_"); - if (!sanitizedValue.matches("^[a-zA-Z_].*")) { + if (OCAML_INVALID_IDENTIFIER_START.matcher(sanitizedValue).find()) { sanitizedValue = "_" + sanitizedValue; } return "`" + capitalize(sanitizedValue); @@ -846,7 +856,7 @@ private CodegenModel buildEnumModel(String enumName, String values) { m.setName(enumName); m.setClassname(enumName); m.setDataType(enumName); - String[] vals = values.split(","); + String[] vals = COMMA_DELIMITER.split(values); if (vals.length == 1) { m.setDefaultValue(ocamlizeEnumValue(vals[0])); } @@ -879,7 +889,7 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List advancedMappingTypes = new HashSet<>(); + /** Matches any character that is not alphanumeric or underscore; used in model name normalization. */ + private static final java.util.regex.Pattern NON_ALNUM_UNDERSCORE = java.util.regex.Pattern.compile("[^0-9a-zA-Z_]"); + /** Matches a single uppercase letter or underscore only (e.g. {@code "A"}, {@code "_"}). */ + private static final java.util.regex.Pattern SINGLE_UPPER_OR_UNDERSCORE = java.util.regex.Pattern.compile("^[A-Z_]$"); + public ObjcClientCodegen() { super(); @@ -445,7 +451,7 @@ public String toModelName(String type) { // model name starts with number /* no need for the fix below as objc model starts with prefix (e.g. SWG) - if (type.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(type).matches()) { LOGGER.warn(type + " (model name starts with number) cannot be used as model name. Renamed to " + camelize("model_" + type)); type = "model_" + type; // e.g. 200Response => Model200Response (after camelize) } @@ -462,7 +468,7 @@ public String toModelName(String type) { * @return model Name in ObjC style guide */ public String toModelNameWithoutReservedWordCheck(String type) { - type = type.replaceAll("[^0-9a-zA-Z_]", "_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + type = NON_ALNUM_UNDERSCORE.matcher(type).replaceAll("_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. // language build-in classes if (typeMapping.keySet().contains(type) || @@ -557,18 +563,18 @@ public String toVarName(String name) { name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. // if it's all upper case, do noting - if (name.matches("^[A-Z_]$")) { + if (SINGLE_UPPER_OR_UNDERSCORE.matcher(name).matches()) { return name; } // if name starting with special word, escape with '_' for (String specialWord : specialWords) { - if (name.matches("(?i:^" + specialWord + ".*)")) + if (PatternCache.get("(?i:^" + specialWord + ".*)").matcher(name).matches()) name = escapeSpecialWord(name); } if (name.startsWith(classPrefix)) { - name = name.replaceFirst(classPrefix, ""); //remove the class prefix, e.g. SWGPet => Pet + name = PatternCache.get(java.util.regex.Pattern.quote(classPrefix)).matcher(name).replaceFirst(""); //remove the class prefix, e.g. SWGPet => Pet } // camelize (lower first character) the variable name @@ -576,7 +582,7 @@ public String toVarName(String name) { name = camelize(name, LOWERCASE_FIRST_LETTER); // for reserved word or word starting with number, prepend `_` - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PerlClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PerlClientCodegen.java index 95cd04247ccc..f1c3226f868b 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PerlClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PerlClientCodegen.java @@ -330,7 +330,7 @@ public String toVarName(String name) { // parameter name starting with number won't compile // need to escape it by appending _ at the beginning - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { name = "_" + name; } return name; @@ -375,7 +375,7 @@ public String toModelName(String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, camelize("model_" + name)); name = "model_" + name; // e.g. 200Response => Model200Response (after camelize) @@ -426,7 +426,7 @@ public String toApiDocFilename(String name) { @Override public String toApiFilename(String name) { // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + name = MINUS.matcher(name).replaceAll("_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. // e.g. phone_number_api.rb => PhoneNumberApi.rb return camelize(name) + "Api"; @@ -457,7 +457,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, underscore(sanitizeName("call_" + operationId))); operationId = "call_" + operationId; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpFlightServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpFlightServerCodegen.java index bd35553b9834..b14617bd54be 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpFlightServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PhpFlightServerCodegen.java @@ -207,7 +207,7 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List extractPlaceholders(String input) { Set variables = new HashSet<>(); - Pattern pattern = Pattern.compile("\\{\\{([^}]*)\\}\\}"); - Matcher matcher = pattern.matcher(input); + Matcher matcher = POSTMAN_PLACEHOLDER_PATTERN.matcher(input); while (matcher.find()) { if (postmanGuidPlaceholderName.equalsIgnoreCase(matcher.group(1))) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PowerShellClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PowerShellClientCodegen.java index d0c603d20a07..8c9448f307bc 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PowerShellClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PowerShellClientCodegen.java @@ -42,6 +42,11 @@ public class PowerShellClientCodegen extends DefaultCodegen implements CodegenConfig { private final Logger LOGGER = LoggerFactory.getLogger(PowerShellClientCodegen.class); + + /** Captures the last path segment of a URL (for extracting PS Gallery package ID). */ + private static final java.util.regex.Pattern GALLERY_URL_LAST_SEGMENT = java.util.regex.Pattern.compile(".*/([^/?]+).*"); + /** Matches two or more consecutive newlines — for normalising example output. */ + private static final java.util.regex.Pattern MULTI_NEWLINE = java.util.regex.Pattern.compile("[\n]{2,}"); @Setter private String packageGuid = "{" + randomUUID().toString().toUpperCase(Locale.ROOT) + "}"; @@ -702,7 +707,7 @@ public void processOpts() { if (StringUtils.isNotBlank(powershellGalleryUrl)) { // get the last segment of the URL // e.g. https://www.powershellgallery.com/packages/PSTwitter => PSTwitter - additionalProperties.put("powershellGalleryId", powershellGalleryUrl.replaceFirst(".*/([^/?]+).*", "$1")); + additionalProperties.put("powershellGalleryId", GALLERY_URL_LAST_SEGMENT.matcher(powershellGalleryUrl).replaceFirst("$1")); } if (additionalProperties.containsKey(CodegenConstants.OPTIONAL_PROJECT_GUID)) { @@ -798,10 +803,10 @@ public String escapeText(String input) { // outer unescape to retain the original multi-byte characters // finally escalate characters avoiding code injection return escapeUnsafeCharacters( - StringEscapeUtils.unescapeJava( + CONTROL_WHITESPACE.matcher(StringEscapeUtils.unescapeJava( StringEscapeUtils.escapeJava(input) - .replace("\\/", "/")) - .replaceAll("[\\t\\n\\r]", " ") + .replace("\\/", "/"))) + .replaceAll(" ") .replace("\"", "\"\"")); } @@ -909,7 +914,7 @@ public String toModelName(String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, camelize("model_" + name)); name = camelize("model_" + name); // e.g. 200Response => Model200Response (after camelize) @@ -992,7 +997,7 @@ public String toParamName(String name) { name = camelize(sanitizeName(name)); // for param name reserved word or word starting with number, append _ - if (paramNameReservedWords.contains(name) || name.matches("^\\d.*")) { + if (paramNameReservedWords.contains(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { LOGGER.warn("{} (reserved word or special variable name) cannot be used in naming. Renamed to {}", name, escapeReservedWord(name)); name = escapeReservedWord(name); @@ -1127,7 +1132,7 @@ public String toVarName(String name) { name = camelize(name); // for reserved word or word starting with number, append _ - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { LOGGER.warn("{} (reserved word or special variable name) cannot be used in naming. Renamed to {}", name, escapeReservedWord(name)); name = escapeReservedWord(name); @@ -1202,7 +1207,7 @@ private String constructExampleCode(CodegenParameter codegenParameter, HashMap modelMaps, HashMap processedModelMap, boolean requiredOnly) { @@ -1541,9 +1546,9 @@ public String toEnumVarName(String name, String datatype) { "UInt16".equals(datatype) || "UInt32".equals(datatype) || "UInt64".equals(datatype) || "Double".equals(datatype) || "Single".equals(datatype) || "Decimal".equals(datatype)) { String varName = name; - varName = varName.replaceAll("-", "MINUS_"); - varName = varName.replaceAll("\\+", "PLUS_"); - varName = varName.replaceAll("\\.", "_DOT_"); + varName = MINUS.matcher(varName).replaceAll("MINUS_"); + varName = PLUS.matcher(varName).replaceAll("PLUS_"); + varName = DOT.matcher(varName).replaceAll("_DOT_"); return "NUMBER_" + varName; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ProtobufSchemaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ProtobufSchemaCodegen.java index 9e1b641b6e9d..023d3e302165 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ProtobufSchemaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ProtobufSchemaCodegen.java @@ -16,6 +16,7 @@ package org.openapitools.codegen.languages; +import com.google.common.base.CaseFormat; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.media.ArraySchema; import io.swagger.v3.oas.models.media.MapSchema; @@ -42,12 +43,10 @@ import java.io.File; import java.util.*; import java.util.Map.Entry; -import java.util.regex.Pattern; import java.util.stream.Collectors; -import com.google.common.base.CaseFormat; - -import static org.openapitools.codegen.utils.StringUtils.*; +import static org.openapitools.codegen.utils.StringUtils.camelize; +import static org.openapitools.codegen.utils.StringUtils.underscore; public class ProtobufSchemaCodegen extends DefaultCodegen implements CodegenConfig { @@ -1545,7 +1544,7 @@ public String toDefaultValue(Schema p) { } } else if (ModelUtils.isStringSchema(p)) { if (p.getDefault() != null) { - if (Pattern.compile("\r\n|\r|\n").matcher(String.valueOf(p.getDefault())).find()) + if (MULTILINE_STRING.matcher(String.valueOf(p.getDefault())).find()) return "'''" + p.getDefault() + "'''"; else return "'" + p.getDefault() + "'"; @@ -1572,7 +1571,7 @@ public String modelFileFolder() { @Override public String toApiFilename(String name) { // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); + name = MINUS.matcher(name).replaceAll("_"); // e.g. PhoneNumber => phone_number return underscore(name) + "_service"; @@ -1611,7 +1610,7 @@ public String toVarName(String name) { name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. // if it's all upper case, convert to lower case - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { name = name.toLowerCase(Locale.ROOT); } @@ -1620,10 +1619,10 @@ public String toVarName(String name) { name = underscore(name); // remove leading underscore - name = name.replaceAll("^_*", ""); + name = LEADING_UNDERSCORES.matcher(name).replaceAll(""); // for reserved word or word starting with number, append _ - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } @@ -1648,7 +1647,7 @@ public String toModelName(String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, camelize("model_" + name)); name = "model_" + name; // e.g. 200Response => Model200Response (after camelize) @@ -1809,7 +1808,7 @@ private int generateFieldNumberFromString(String name) throws ProtoBufIndexCompu // Max value from developers.google.com/protocol-buffers/docs/proto3#assigning_field_numbers int fieldNumber = Math.abs(name.hashCode() % 536870911); if (19000 <= fieldNumber && fieldNumber <= 19999) { - LOGGER.error("Generated field number is in reserved range (19000, 19999) for %s, %d", name, fieldNumber); + LOGGER.error("Generated field number is in reserved range (19000, 19999) for {}, {}", name, fieldNumber); throw new ProtoBufIndexComputationException("Generated field number is in reserved range (19000, 19999)."); } return fieldNumber; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java index 924263f217b3..8ed782c63cdf 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java @@ -310,7 +310,7 @@ public void processOpts() { } // If the package name consists of dots(openapi.client), then we need to create the directory structure like openapi/client with __init__ files. - String[] packageNameSplits = packageName.split("\\."); + String[] packageNameSplits = DOT.split(packageName); String currentPackagePath = ""; for (int i = 0; i < packageNameSplits.length - 1; i++) { if (i > 0) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonPydanticV1ClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonPydanticV1ClientCodegen.java index 0834b51cd4cf..bb3178be50ba 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonPydanticV1ClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonPydanticV1ClientCodegen.java @@ -314,7 +314,7 @@ public void processOpts() { } // If the package name consists of dots(openapi.client), then we need to create the directory structure like openapi/client with __init__ files. - String[] packageNameSplits = packageName.split("\\."); + String[] packageNameSplits = DOT.split(packageName); String currentPackagePath = ""; for (int i = 0; i < packageNameSplits.length - 1; i++) { if (i > 0) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RClientCodegen.java index 188436a75eb5..dd06b7cc7fb4 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RClientCodegen.java @@ -38,7 +38,6 @@ import java.io.IOException; import java.io.Writer; import java.util.*; -import java.util.regex.Pattern; import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER; import static org.openapitools.codegen.utils.StringUtils.camelize; @@ -400,10 +399,10 @@ public String toParamName(String name) { } // replace - with _ e.g. created-at => created_at - name = sanitizeName(name.replaceAll("-", "_")); + name = sanitizeName(MINUS.matcher(name).replaceAll("_")); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) return name; // convert variable name to snake case @@ -415,7 +414,7 @@ public String toParamName(String name) { name = "var_" + name; // for reserved word or word starting with number, append _ - if (name.matches("^\\d.*")) + if (STARTS_WITH_DIGIT.matcher(name).matches()) name = "var_" + name; return name; @@ -489,7 +488,7 @@ public String toModelName(String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, camelize("model_" + name)); name = "model_" + name; // e.g. 200Response => Model200Response (after camelize) @@ -502,7 +501,7 @@ public String toModelName(String name) { @Override public String toApiFilename(String name) { // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + name = MINUS.matcher(name).replaceAll("_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. // e.g. PetApi.r => pet_api.r return underscore(name + "_api"); @@ -710,9 +709,9 @@ public String toEnumVarName(String name, String datatype) { // number if ("int".equals(datatype) || "double".equals(datatype) || "float".equals(datatype)) { String varName = name; - varName = varName.replaceAll("-", "MINUS_"); - varName = varName.replaceAll("\\+", "PLUS_"); - varName = varName.replaceAll("\\.", "_DOT_"); + varName = MINUS.matcher(varName).replaceAll("MINUS_"); + varName = PLUS.matcher(varName).replaceAll("PLUS_"); + varName = DOT.matcher(varName).replaceAll("_DOT_"); return varName; } @@ -723,10 +722,10 @@ public String toEnumVarName(String name, String datatype) { // string String enumName = sanitizeName(underscore(name).toUpperCase(Locale.ROOT)); - enumName = enumName.replaceFirst("^_", ""); - enumName = enumName.replaceFirst("_$", ""); + enumName = FIRST_LEADING_UNDERSCORE.matcher(enumName).replaceFirst(""); + enumName = LAST_TRAILING_UNDERSCORE.matcher(enumName).replaceFirst(""); - if (isReservedWord(enumName) || enumName.matches("\\d.*")) { // reserved word or starts with number + if (isReservedWord(enumName) || STARTS_WITH_DIGIT.matcher(enumName).matches()) { // reserved word or starts with number return escapeReservedWord(enumName); } else { return enumName; @@ -740,7 +739,7 @@ public String toEnumName(CodegenProperty property) { // remove [] for array or map of enum enumName = enumName.replace("[]", ""); - if (enumName.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(enumName).matches()) { // starts with number return "_" + enumName; } else { return enumName; @@ -859,7 +858,7 @@ public String toDefaultValue(Schema p) { } } else if (ModelUtils.isStringSchema(p)) { if (p.getDefault() != null) { - if (Pattern.compile("\r\n|\r|\n").matcher((String.valueOf(p.getDefault()))).find()) + if (MULTILINE_STRING.matcher((String.valueOf(p.getDefault()))).find()) return "'''" + p.getDefault().toString() + "'''"; else return "\"" + ((String.valueOf(p.getDefault()))).replaceAll("\"", "\\\"") + "\""; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RubyClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RubyClientCodegen.java index bd8595749bb2..328be26b8dd1 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RubyClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RubyClientCodegen.java @@ -340,7 +340,7 @@ public String getHelp() { */ @SuppressWarnings("static-method") public String generateModuleName(String gemName) { - return camelize(gemName.replaceAll("[^\\w]+", "_")); + return camelize(NON_WORD_PLUS.matcher(gemName).replaceAll("_")); } /** @@ -351,7 +351,7 @@ public String generateModuleName(String gemName) { */ @SuppressWarnings("static-method") public String generateGemName(String moduleName) { - return underscore(moduleName.replaceAll("[^\\w]+", "")); + return underscore(NON_WORD_PLUS.matcher(moduleName).replaceAll("")); } @Override @@ -442,7 +442,7 @@ public String toModelName(final String name) { } // model name starts with number - if (modelName.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(modelName).matches()) { LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", modelName, camelize("model_" + modelName)); modelName = "model_" + modelName; // e.g. 200Response => Model200Response (after camelize) @@ -524,10 +524,10 @@ public String toEnumVarName(String name, String datatype) { // string String enumName = sanitizeName(underscore(name).toUpperCase(Locale.ROOT)); - enumName = enumName.replaceFirst("^_", ""); - enumName = enumName.replaceFirst("_$", ""); + enumName = FIRST_LEADING_UNDERSCORE.matcher(enumName).replaceFirst(""); + enumName = LAST_TRAILING_UNDERSCORE.matcher(enumName).replaceFirst(""); - if (enumName.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(enumName).matches()) { // starts with number return NUMERIC_ENUM_PREFIX + enumName; } else { return enumName; @@ -541,10 +541,10 @@ public String toEnumName(CodegenProperty property) { } String enumName = underscore(toModelName(property.name)).toUpperCase(Locale.ROOT); - enumName = enumName.replaceFirst("^_", ""); - enumName = enumName.replaceFirst("_$", ""); + enumName = FIRST_LEADING_UNDERSCORE.matcher(enumName).replaceFirst(""); + enumName = LAST_TRAILING_UNDERSCORE.matcher(enumName).replaceFirst(""); - if (enumName.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(enumName).matches()) { // starts with number return NUMERIC_ENUM_PREFIX + enumName; } else { return enumName; @@ -574,7 +574,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, underscore(sanitizeName("call_" + operationId))); operationId = "call_" + operationId; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RubyOnRailsServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RubyOnRailsServerCodegen.java index b3f484f375cd..e4db03a7668f 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RubyOnRailsServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RubyOnRailsServerCodegen.java @@ -265,7 +265,7 @@ public String toModelFilename(String name) { @Override public String toApiFilename(String name) { // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + name = MINUS.matcher(name).replaceAll("_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. // e.g. DefaultController => defaults_controller.rb return underscore(name) + "_controller"; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RubySinatraServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RubySinatraServerCodegen.java index 1e51a349e0db..c32e2e70db57 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RubySinatraServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RubySinatraServerCodegen.java @@ -157,7 +157,7 @@ public String toModelFilename(String name) { @Override public String toApiFilename(String name) { // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. + name = MINUS.matcher(name).replaceAll("_"); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'. // e.g. PhoneNumberApi.rb => phone_number_api.rb return underscore(name) + "_api"; @@ -165,7 +165,7 @@ public String toApiFilename(String name) { @Override public String toApiName(String name) { - if (name.length() == 0) { + if (name.isEmpty()) { return "DefaultApi"; } // e.g. phone_number_api => PhoneNumberApi diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java index 13f410c2d3d8..095cb333b94c 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java @@ -475,7 +475,7 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation // // Construct a Rust constant (uppercase) token name, and ensure it's // unique using a numeric tie-breaker if required. - String basePathId = sanitizeName(op.path.replace("/", "_").replace("{", "").replace("}", "").replaceAll("^_", "")).toUpperCase(Locale.ROOT); + String basePathId = sanitizeName(FIRST_LEADING_UNDERSCORE.matcher(op.path.replace("/", "_").replace("{", "").replace("}", "")).replaceAll("")).toUpperCase(Locale.ROOT); String pathId = basePathId; int pathIdTiebreaker = 2; boolean found = false; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegenDeprecated.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegenDeprecated.java index 96f28a9b14c6..44e197a9ca2c 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegenDeprecated.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegenDeprecated.java @@ -462,7 +462,7 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation // // Construct a Rust constant (uppercase) token name, and ensure it's // unique using a numeric tie-breaker if required. - String basePathId = sanitizeName(op.path.replace("/", "_").replace("{", "").replace("}", "").replaceAll("^_", "")).toUpperCase(Locale.ROOT); + String basePathId = sanitizeName(FIRST_LEADING_UNDERSCORE.matcher(op.path.replace("/", "_").replace("{", "").replace("}", "")).replaceAll("")).toUpperCase(Locale.ROOT); String pathId = basePathId; int pathIdTiebreaker = 2; boolean found = false; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaAkkaClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaAkkaClientCodegen.java index 749ea34f98ef..f1608604cf23 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaAkkaClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaAkkaClientCodegen.java @@ -305,7 +305,7 @@ public String toDefaultValue(Schema p) { private static class JavadocLambda extends CustomLambda { @Override public String formatFragment(String fragment) { - final String[] lines = fragment.split("\\r?\\n"); + final String[] lines = DefaultCodegen.SPLIT_ON_NEWLINE.split(fragment); final StringBuilder sb = new StringBuilder(); sb.append(" /**\n"); for (String line : lines) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaCaskServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaCaskServerCodegen.java index 7179f7374817..0e51eee3b5ec 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaCaskServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaCaskServerCodegen.java @@ -45,6 +45,11 @@ public class ScalaCaskServerCodegen extends AbstractScalaCodegen implements Code // citizen of cask private static final String AdditionalPropertiesType = "Value"; + /** Matches any character that is not alphanumeric, underscore, ], or [. */ + private static final Pattern NON_IDENTIFIER_CHAR = Pattern.compile("[^a-zA-Z0-9_\\]\\[]"); + /** Matches the inner type of a collection, e.g. {@code List[Foo]} → {@code Foo}. */ + private static final Pattern INNER_COLLECTION_TYPE = Pattern.compile(".*\\[(.*)]"); + private final Logger LOGGER = LoggerFactory.getLogger(ScalaCaskServerCodegen.class); @Override @@ -323,7 +328,7 @@ public String apiInterfaceFileFolder() { static String asMethod(final String input) { // Remove all non-alphanumeric characters using regex - String alphanumeric = input.replaceAll("[^a-zA-Z0-9]", ""); + String alphanumeric = NON_ALPHANUMERIC_CHAR.matcher(input).replaceAll(""); // Ensure the method name doesn't start with a digit if (alphanumeric.isEmpty()) { @@ -940,7 +945,7 @@ private void postProcessProperty(final CodegenProperty p) { * type: string * }}} */ - if (p.datatypeWithEnum != null && p.datatypeWithEnum.matches(".*[^a-zA-Z0-9_\\]\\[].*")) { + if (p.datatypeWithEnum != null && NON_IDENTIFIER_CHAR.matcher(p.datatypeWithEnum).find()) { p.datatypeWithEnum = fixBackTicks(p.datatypeWithEnum); } @@ -1238,11 +1243,11 @@ private static String chompSuffix(String str, String suffix) { } private static boolean hasBrackets(String str) { - return str.matches("^\\{(.*)\\}$"); + return IS_PATH_PARAM.matcher(str).matches(); } static String containerType(String dataType) { - String fixedForList = dataType.replaceAll(".*\\[(.*)\\]", "$1"); + String fixedForList = INNER_COLLECTION_TYPE.matcher(dataType).replaceAll("$1"); // if it is a map, we want the value type final String[] parts = fixedForList.split(","); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaFinchServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaFinchServerCodegen.java index e6144e081f92..d51074d73f40 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaFinchServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaFinchServerCodegen.java @@ -32,6 +32,10 @@ import java.util.*; public class ScalaFinchServerCodegen extends DefaultCodegen implements CodegenConfig { + + /** Matches the non-bracket prefix of a collection type, e.g. {@code "List"} in {@code "List[String]"}. */ + private static final java.util.regex.Pattern NON_BRACKET_PREFIX = java.util.regex.Pattern.compile("^[^\\[]+"); + private final Logger LOGGER = LoggerFactory.getLogger(ScalaFinchServerCodegen.class); protected String invokerPackage = "org.openapitools.client"; protected String groupId = "org.openapitools"; @@ -383,7 +387,7 @@ private void generateScalaPath(CodegenOperation op) { for (String item : items) { - if (item.matches("^\\{(.*)}$")) { // wrap in {} + if (IS_PATH_PARAM.matcher(item).matches()) { // wrap in {} // find the datatype of the parameter final CodegenParameter cp = op.pathParams.get(pathParamIndex); @@ -429,7 +433,7 @@ private void generateInputParameters(CodegenOperation op) { p.vendorExtensions.put("x-codegen-normalized-input-type", p.dataType); } else if (p.isContainer || p.isArray) { p.vendorExtensions.put("x-codegen-normalized-path-type", toPathParameter(p, "params", false)); - p.vendorExtensions.put("x-codegen-normalized-input-type", p.dataType.replaceAll("^[^\\[]+", "Seq")); + p.vendorExtensions.put("x-codegen-normalized-input-type", NON_BRACKET_PREFIX.matcher(p.dataType).replaceAll("Seq")); } else if (p.isQueryParam) { p.vendorExtensions.put("x-codegen-normalized-path-type", toPathParameter(p, "param", true)); p.vendorExtensions.put("x-codegen-normalized-input-type", toInputParameter(p)); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaGatlingCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaGatlingCodegen.java index 3a9eb9b1c1c7..2dfef0551e46 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaGatlingCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaGatlingCodegen.java @@ -268,7 +268,7 @@ public void preprocessOpenAPI(OpenAPI openAPI) { if (!operation.getExtensions().keySet().contains("x-gatling-path")) { if (pathname.contains("{")) { - String gatlingPath = pathname.replaceAll("\\{", "\\$\\{"); + String gatlingPath = LEFT_CURLY_BRACE.matcher(pathname).replaceAll("\\${"); operation.addExtension("x-gatling-path", gatlingPath); } else { operation.addExtension("x-gatling-path", pathname); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaHttp4sServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaHttp4sServerCodegen.java index fe8098feb1ce..e92778fab32e 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaHttp4sServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaHttp4sServerCodegen.java @@ -787,7 +787,7 @@ private void generateScalaPath(CodegenOperation op) { for (String item : items) { - if (item.matches("^\\{(.*)}$")) { // wrap in {} + if (IS_PATH_PARAM.matcher(item).matches()) { // wrap in {} // find the datatype of the parameter final CodegenParameter cp = op.pathParams.get(pathParamIndex); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaLagomServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaLagomServerCodegen.java index 57efc5671b20..dbb5a30d004b 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaLagomServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaLagomServerCodegen.java @@ -190,7 +190,7 @@ public ModelsMap postProcessModelsEnum(ModelsMap objs) { for (final ListIterator i = enumValues.listIterator(); i.hasNext(); ) { final String element = String.valueOf(i.next()); - i.set(element.replaceAll("^\"|\"$", "")); + i.set(ENCLOSING_QUOTES.matcher(element).replaceAll("")); } } } @@ -222,7 +222,7 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List ops = operations.getOperation(); for (CodegenOperation operation : ops) { - Pattern pathVariableMatcher = Pattern.compile("\\{([^}]+)}"); - Matcher match = pathVariableMatcher.matcher(operation.path); + Matcher match = PATH_PARAMETER.matcher(operation.path); while (match.find()) { String completeMatch = match.group(); String replacement = ":" + camelize(match.group(1), LOWERCASE_FIRST_LETTER); @@ -373,8 +372,8 @@ public String toEnumVarName(String value, String datatype) { return "EMPTY"; } - String var = camelize(value.replaceAll("\\W+", "_")); - if (var.matches("\\d.*")) { + String var = camelize(NON_WORD_PLUS.matcher(value).replaceAll("_")); + if (STARTS_WITH_DIGIT.matcher(var).matches()) { return "_" + var; } else { return var; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttp4ClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttp4ClientCodegen.java index b628cb615500..20ae0974ee0b 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttp4ClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttp4ClientCodegen.java @@ -585,7 +585,7 @@ public String getInvokerPackage(Map additionalProperties) { private static class JavadocLambda extends CustomLambda { @Override public String formatFragment(String fragment) { - final String[] lines = fragment.split("\\r?\\n"); + final String[] lines = DefaultCodegen.SPLIT_ON_NEWLINE.split(fragment); final StringBuilder sb = new StringBuilder(); sb.append(" /**\n"); for (String line : lines) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttp4JsoniterClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttp4JsoniterClientCodegen.java index c47a7a390680..94fd2cb535cb 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttp4JsoniterClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttp4JsoniterClientCodegen.java @@ -670,7 +670,7 @@ public String getInvokerPackage(Map additionalProperties) { private static class JavadocLambda extends CustomLambda { @Override public String formatFragment(String fragment) { - final String[] lines = fragment.split("\\r?\\n"); + final String[] lines = DefaultCodegen.SPLIT_ON_NEWLINE.split(fragment); final StringBuilder sb = new StringBuilder(); sb.append(" /**\n"); for (String line : lines) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttpClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttpClientCodegen.java index f4aa0e66327d..ac1f0acae9e5 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttpClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ScalaSttpClientCodegen.java @@ -585,7 +585,7 @@ public String getInvokerPackage(Map additionalProperties) { private static class JavadocLambda extends CustomLambda { @Override public String formatFragment(String fragment) { - final String[] lines = fragment.split("\\r?\\n"); + final String[] lines = DefaultCodegen.SPLIT_ON_NEWLINE.split(fragment); final StringBuilder sb = new StringBuilder(); sb.append(" /**\n"); for (String line : lines) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java index 57ee37e1221b..ab15dd6a6ab6 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java @@ -768,7 +768,7 @@ public String toModelName(String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { // e.g. 200Response => Model200Response (after camelize) String modelName = "Model" + name; LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, @@ -872,7 +872,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, camelize(sanitizeName("call_" + operationId), LOWERCASE_FIRST_LETTER)); operationId = camelize(sanitizeName("call_" + operationId), LOWERCASE_FIRST_LETTER); } @@ -891,7 +891,7 @@ public String toVarName(String name) { name = sanitizeName(name); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } @@ -905,7 +905,7 @@ public String toVarName(String name) { } // for words starting with number, append _ - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { name = "_" + name; } @@ -923,10 +923,10 @@ public String toParamName(String name) { name = sanitizeName(name); // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); + name = MINUS.matcher(name).replaceAll("_"); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } @@ -940,7 +940,7 @@ public String toParamName(String name) { } // for words starting with number, append _ - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { name = "_" + name; } @@ -1021,7 +1021,7 @@ public String toEnumVarName(String name, String datatype) { } // Prefix with underscore if name starts with number - if (name.matches("\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { return "_" + replaceSpecialCharacters(camelize(name, LOWERCASE_FIRST_LETTER)); } @@ -1032,7 +1032,7 @@ public String toEnumVarName(String name, String datatype) { // Camelize only when we have a structure defined below boolean camelized = false; - if (name.matches("[A-Z][a-z0-9]+[a-zA-Z0-9]*")) { + if (CAMEL_INITIAL.matcher(name).matches()) { name = camelize(name, LOWERCASE_FIRST_LETTER); camelized = true; } @@ -1051,8 +1051,8 @@ public String toEnumVarName(String name, String datatype) { } char[] separators = {'-', '_', ' ', ':', '(', ')'}; - return camelize(replaceSpecialCharacters(WordUtils.capitalizeFully(StringUtils.lowerCase(name), separators) - .replaceAll("[-_ :\\(\\)]", "")), + return camelize(replaceSpecialCharacters(DASH_UNDERSCORE_SPACE_COLON_PARENTHESES.matcher(WordUtils.capitalizeFully(StringUtils.lowerCase(name), separators)) + .replaceAll("")), LOWERCASE_FIRST_LETTER); } @@ -1071,7 +1071,7 @@ private String replaceSpecialCharacters(String name) { } // Fallback, replace unknowns with underscore. - name = name.replaceAll("\\W+", "_"); + name = NON_WORD_PLUS.matcher(name).replaceAll("_"); return name; } @@ -1120,7 +1120,7 @@ public String toEnumName(CodegenProperty property) { // TODO: toModelName already does something for names starting with number, // so this code is probably never called - if (enumName.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(enumName).matches()) { // starts with number return "_" + enumName; } else { return enumName; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift6ClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift6ClientCodegen.java index caec04adb227..7027918f56a7 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift6ClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift6ClientCodegen.java @@ -811,7 +811,7 @@ public String toModelName(String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { // e.g. 200Response => Model200Response (after camelize) String modelName = "Model" + name; LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, @@ -915,7 +915,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, camelize(sanitizeName("call_" + operationId), LOWERCASE_FIRST_LETTER)); operationId = camelize(sanitizeName("call_" + operationId), LOWERCASE_FIRST_LETTER); } @@ -934,7 +934,7 @@ public String toVarName(String name) { name = sanitizeName(name); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } @@ -948,7 +948,7 @@ public String toVarName(String name) { } // for words starting with number, append _ - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { name = "_" + name; } @@ -966,10 +966,10 @@ public String toParamName(String name) { name = sanitizeName(name); // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); + name = MINUS.matcher(name).replaceAll("_"); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } @@ -983,7 +983,7 @@ public String toParamName(String name) { } // for words starting with number, append _ - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { name = "_" + name; } @@ -1064,7 +1064,7 @@ public String toEnumVarName(String name, String datatype) { } // Prefix with underscore if name starts with number - if (name.matches("\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { return "_" + replaceSpecialCharacters(camelize(name, LOWERCASE_FIRST_LETTER)); } @@ -1075,7 +1075,7 @@ public String toEnumVarName(String name, String datatype) { // Camelize only when we have a structure defined below boolean camelized = false; - if (name.matches("[A-Z][a-z0-9]+[a-zA-Z0-9]*")) { + if (CAMEL_INITIAL.matcher(name).matches()) { name = camelize(name, LOWERCASE_FIRST_LETTER); camelized = true; } @@ -1094,8 +1094,8 @@ public String toEnumVarName(String name, String datatype) { } char[] separators = {'-', '_', ' ', ':', '(', ')'}; - return camelize(replaceSpecialCharacters(WordUtils.capitalizeFully(StringUtils.lowerCase(name), separators) - .replaceAll("[-_ :\\(\\)]", "")), + return camelize(replaceSpecialCharacters(DASH_UNDERSCORE_SPACE_COLON_PARENTHESES.matcher(WordUtils.capitalizeFully(StringUtils.lowerCase(name), separators)) + .replaceAll("")), LOWERCASE_FIRST_LETTER); } @@ -1125,7 +1125,7 @@ private String replaceSpecialCharacters(String name) { } // Fallback, replace unknowns with underscore. - name = name.replaceAll("\\W+", "_"); + name = NON_WORD_PLUS.matcher(name).replaceAll("_"); return name; } @@ -1174,7 +1174,7 @@ public String toEnumName(CodegenProperty property) { // TODO: toModelName already does something for names starting with number, // so this code is probably never called - if (enumName.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(enumName).matches()) { // starts with number return "_" + enumName; } else { return enumName; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SwiftCombineClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SwiftCombineClientCodegen.java index d321fd0d856d..9ef3aef332b0 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SwiftCombineClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SwiftCombineClientCodegen.java @@ -51,6 +51,9 @@ public class SwiftCombineClientCodegen extends DefaultCodegen implements CodegenConfig { private final Logger LOGGER = LoggerFactory.getLogger(SwiftCombineClientCodegen.class); + /** Matches a Swift identifier starting with an uppercase letter followed by lowercase+digits. */ + // CAMEL_INITIAL pattern is inherited from DefaultCodegen + public static final String PROJECT_NAME = "projectName"; public static final String MAP_FILE_BINARY_TO_DATA = "mapFileBinaryToData"; @Setter protected String projectName = "OpenAPIClient"; @@ -369,7 +372,7 @@ public String toModelName(String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { // e.g. 200Response => Model200Response (after camelize) String modelName = "Model" + name; LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, @@ -465,7 +468,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, camelize(sanitizeName("call_" + operationId), LOWERCASE_FIRST_LETTER)); operationId = camelize(sanitizeName("call_" + operationId), LOWERCASE_FIRST_LETTER); } @@ -480,7 +483,7 @@ public String toVarName(String name) { name = sanitizeName(name); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } @@ -494,7 +497,7 @@ public String toVarName(String name) { } // for words starting with number, append _ - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { name = "_" + name; } @@ -507,10 +510,10 @@ public String toParamName(String name) { name = sanitizeName(name); // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); + name = MINUS.matcher(name).replaceAll("_"); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } @@ -524,7 +527,7 @@ public String toParamName(String name) { } // for words starting with number, append _ - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { name = "_" + name; } @@ -595,7 +598,7 @@ public String toEnumVarName(String name, String datatype) { // Camelize only when we have a structure defined below Boolean camelized = false; - if (name.matches("[A-Z][a-z0-9]+[a-zA-Z0-9]*")) { + if (CAMEL_INITIAL.matcher(name).matches()) { name = camelize(name, LOWERCASE_FIRST_LETTER); camelized = true; } @@ -610,9 +613,9 @@ public String toEnumVarName(String name, String datatype) { if ("Int".equals(datatype) || "Int32".equals(datatype) || "Int64".equals(datatype) || "Float".equals(datatype) || "Double".equals(datatype)) { String varName = "number" + camelize(name); - varName = varName.replaceAll("-", "minus"); - varName = varName.replaceAll("\\+", "plus"); - varName = varName.replaceAll("\\.", "dot"); + varName = MINUS.matcher(varName).replaceAll("minus"); + varName = PLUS.matcher(varName).replaceAll("plus"); + varName = DOT.matcher(varName).replaceAll("dot"); return varName; } @@ -623,8 +626,8 @@ public String toEnumVarName(String name, String datatype) { } char[] separators = {'-', '_', ' ', ':', '(', ')'}; - return camelize(WordUtils.capitalizeFully(StringUtils.lowerCase(name), separators) - .replaceAll("[-_ :\\(\\)]", ""), + return camelize(DASH_UNDERSCORE_SPACE_COLON_PARENTHESES.matcher(WordUtils.capitalizeFully(StringUtils.lowerCase(name), separators)) + .replaceAll(""), LOWERCASE_FIRST_LETTER); } @@ -646,7 +649,7 @@ public String toEnumName(CodegenProperty property) { // TODO: toModelName already does something for names starting with number, // so this code is probably never called - if (enumName.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(enumName).matches()) { // starts with number return "_" + enumName; } else { return enumName; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TerraformProviderCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TerraformProviderCodegen.java index 7cc6bcfbcf98..4b51a7e97056 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TerraformProviderCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TerraformProviderCodegen.java @@ -24,11 +24,13 @@ import org.openapitools.codegen.model.ModelsMap; import org.openapitools.codegen.model.OperationMap; import org.openapitools.codegen.model.OperationsMap; +import org.openapitools.codegen.utils.PatternCache; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.util.*; +import java.util.regex.Pattern; import static org.openapitools.codegen.utils.StringUtils.camelize; import static org.openapitools.codegen.utils.StringUtils.underscore; @@ -37,6 +39,9 @@ public class TerraformProviderCodegen extends AbstractGoCodegen { private final Logger LOGGER = LoggerFactory.getLogger(TerraformProviderCodegen.class); + /** Case-insensitive match of a trailing "api" suffix. */ + private static final Pattern API_SUFFIX = Pattern.compile("(?i)api$"); + public static final String PROVIDER_NAME = "providerName"; public static final String PROVIDER_ADDRESS = "providerAddress"; public static final String PROVIDER_VERSION = "providerVersion"; @@ -296,7 +301,7 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List id) if (!idResolved) { - String strippedId = idField.replaceFirst("(?i)^" + resourceName, ""); + String strippedId = PatternCache.get("(?i)^" + java.util.regex.Pattern.quote(resourceName)).matcher(idField).replaceFirst(""); if (!strippedId.isEmpty()) { for (CodegenProperty prop : model.vars) { if (prop.baseName.equalsIgnoreCase(strippedId)) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java index 8190aa46c700..587409afcc73 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java @@ -48,9 +48,9 @@ public class TypeScriptAngularClientCodegen extends AbstractTypeScriptClientCodegen { private final Logger LOGGER = LoggerFactory.getLogger(TypeScriptAngularClientCodegen.class); - private static String CLASS_NAME_PREFIX_PATTERN = "^[a-zA-Z0-9]*$"; - private static String CLASS_NAME_SUFFIX_PATTERN = "^[a-zA-Z0-9]*$"; - private static String FILE_NAME_SUFFIX_PATTERN = "^[a-zA-Z0-9.-]*$"; + private static final java.util.regex.Pattern CLASS_NAME_PREFIX_PATTERN = java.util.regex.Pattern.compile("^[a-zA-Z0-9]*$"); + private static final java.util.regex.Pattern CLASS_NAME_SUFFIX_PATTERN = java.util.regex.Pattern.compile("^[a-zA-Z0-9]*$"); + private static final java.util.regex.Pattern FILE_NAME_SUFFIX_PATTERN = java.util.regex.Pattern.compile("^[a-zA-Z0-9.-]*$"); public static enum QUERY_PARAM_OBJECT_FORMAT_TYPE {dot, json, key} @@ -657,7 +657,7 @@ public String removeModelPrefixSuffix(String name) { * @param value The value that is being validated. */ private void validateFileSuffixArgument(String argument, String value) { - if (!value.matches(FILE_NAME_SUFFIX_PATTERN)) { + if (!FILE_NAME_SUFFIX_PATTERN.matcher(value).matches()) { throw new IllegalArgumentException( String.format(Locale.ROOT, "%s file suffix only allows '.', '-' and alphanumeric characters.", argument) ); @@ -672,7 +672,7 @@ private void validateFileSuffixArgument(String argument, String value) { * @param value The value that is being validated. */ private void validateClassPrefixArgument(String argument, String value) { - if (!value.matches(CLASS_NAME_PREFIX_PATTERN)) { + if (!CLASS_NAME_PREFIX_PATTERN.matcher(value).matches()) { throw new IllegalArgumentException( String.format(Locale.ROOT, "%s class prefix only allows alphanumeric characters.", argument) ); @@ -687,7 +687,7 @@ private void validateClassPrefixArgument(String argument, String value) { * @param value The value that is being validated. */ private void validateClassSuffixArgument(String argument, String value) { - if (!value.matches(CLASS_NAME_SUFFIX_PATTERN)) { + if (!CLASS_NAME_SUFFIX_PATTERN.matcher(value).matches()) { throw new IllegalArgumentException( String.format(Locale.ROOT, "%s class suffix only allows alphanumeric characters.", argument) ); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAxiosClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAxiosClientCodegen.java index 6898d3d7ccfd..e61571a7be12 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAxiosClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAxiosClientCodegen.java @@ -127,8 +127,8 @@ private static String getRelativeToRoot(String path) { @Override public void processOpts() { super.processOpts(); - tsModelPackage = modelPackage.replaceAll("\\.", "/"); - String tsApiPackage = apiPackage.replaceAll("\\.", "/"); + tsModelPackage = DOT.matcher(modelPackage).replaceAll("/"); + String tsApiPackage = DOT.matcher(apiPackage).replaceAll("/"); String modelRelativeToRoot = getRelativeToRoot(tsModelPackage); String apiRelativeToRoot = getRelativeToRoot(tsApiPackage); @@ -270,7 +270,7 @@ public ModelsMap postProcessModels(ModelsMap objs) { } // Deduce the model file name in kebab case - cm.classFilename = cm.classname.replaceAll("([a-z0-9])([A-Z])", "$1-$2").toLowerCase(Locale.ROOT); + cm.classFilename = CAMEL_CASE_BOUNDARY.matcher(cm.classname).replaceAll("$1-$2").toLowerCase(Locale.ROOT); //processed enum names if (!withoutPrefixEnums) { @@ -299,7 +299,7 @@ public ModelsMap postProcessModels(ModelsMap objs) { String tsImport = tsModelPackage + "/" + javaImport; m.put("tsImport", tsImport); m.put("class", javaImport); - m.put("filename", javaImport.replaceAll("([a-z0-9])([A-Z])", "$1-$2").toLowerCase(Locale.ROOT)); + m.put("filename", CAMEL_CASE_BOUNDARY.matcher(javaImport).replaceAll("$1-$2").toLowerCase(Locale.ROOT)); } return objs; } @@ -329,12 +329,12 @@ public String toRegularExpression(String pattern) { @Override public String toModelFilename(String name) { - return super.toModelFilename(name).replaceAll("([a-z0-9])([A-Z])", "$1-$2").toLowerCase(Locale.ROOT); + return CAMEL_CASE_BOUNDARY.matcher(super.toModelFilename(name)).replaceAll("$1-$2").toLowerCase(Locale.ROOT); } @Override public String toApiFilename(String name) { - return super.toApiFilename(name).replaceAll("([a-z0-9])([A-Z])", "$1-$2").toLowerCase(Locale.ROOT); + return CAMEL_CASE_BOUNDARY.matcher(super.toApiFilename(name)).replaceAll("$1-$2").toLowerCase(Locale.ROOT); } private void addNpmPackageGeneration() { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptClientCodegen.java index 085461f446ea..8e8a4aba88b8 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptClientCodegen.java @@ -49,7 +49,6 @@ import java.time.format.DateTimeFormatter; import java.util.*; import java.util.regex.Matcher; -import java.util.regex.Pattern; import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER; import static org.openapitools.codegen.utils.OnceLogger.once; @@ -60,6 +59,9 @@ public class TypeScriptClientCodegen extends AbstractTypeScriptClientCodegen implements CodegenConfig { private final Logger LOGGER = LoggerFactory.getLogger(TypeScriptClientCodegen.class); + /** Splits a TypeScript composed-type string on {@code |}, {@code &}, {@code <}, or {@code >}. */ + private static final java.util.regex.Pattern COMPOSED_TYPE_DELIMITERS = java.util.regex.Pattern.compile("[|&<>]"); + private static final String FRAMEWORK_SWITCH = "framework"; private static final String FRAMEWORK_SWITCH_DESC = "Specify the framework which should be used in the client code."; private static final String[] FRAMEWORKS = {"fetch-api", "jquery"}; @@ -262,7 +264,7 @@ public String toParamName(String name) { } // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } @@ -279,7 +281,7 @@ public String toVarName(String name) { } // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } @@ -316,20 +318,20 @@ public String toEnumVarName(String name, String datatype) { if ("number".equals(datatype)) { String varName = "NUMBER_" + name; - varName = varName.replaceAll("-", "MINUS_"); - varName = varName.replaceAll("\\+", "PLUS_"); - varName = varName.replaceAll("\\.", "_DOT_"); + varName = MINUS.matcher(varName).replaceAll("MINUS_"); + varName = PLUS.matcher(varName).replaceAll("PLUS_"); + varName = DOT.matcher(varName).replaceAll("_DOT_"); return varName; } // string String enumName = sanitizeName(name); - enumName = enumName.replaceFirst("^_", ""); - enumName = enumName.replaceFirst("_$", ""); + enumName = FIRST_LEADING_UNDERSCORE.matcher(enumName).replaceFirst(""); + enumName = LAST_TRAILING_UNDERSCORE.matcher(enumName).replaceFirst(""); enumName = getNameWithEnumPropertyNaming(enumName); - if (enumName.matches("\\d.*")) { // starts with number + if (STARTS_WITH_DIGIT.matcher(enumName).matches()) { // starts with number return "_" + enumName; } @@ -614,8 +616,7 @@ protected Object getObjectExample(Schema sc) { * @return quoted string */ private String ensureQuotes(String in) { - Pattern pattern = Pattern.compile("\r\n|\r|\n"); - Matcher matcher = pattern.matcher(in); + Matcher matcher = MULTILINE_STRING.matcher(in); if (matcher.find()) { // if a string has a new line in it add backticks to make it a typescript multiline string return "`" + in + "`"; @@ -1133,6 +1134,6 @@ protected void addImport(Set importsToBeAddedTo, String type) { * @return list of types */ protected String[] splitComposedTypes(String type) { - return type.replace(" ", "").split("[|&<>]"); + return COMPOSED_TYPE_DELIMITERS.split(type.replace(" ", "")); } } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java index e66f40d55342..3375dbf2dd6f 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java @@ -215,11 +215,11 @@ public void setSagasAndRecords(Boolean sagasAndRecords) { } public String getPassthroughSuffix() { - return detectPassthroughModelsWithSuffixAndField != null ? detectPassthroughModelsWithSuffixAndField.split("\\.")[0] : null; + return detectPassthroughModelsWithSuffixAndField != null ? DOT.split(detectPassthroughModelsWithSuffixAndField)[0] : null; } public String getPassthroughField() { - return detectPassthroughModelsWithSuffixAndField != null ? detectPassthroughModelsWithSuffixAndField.split("\\.")[1] : null; + return detectPassthroughModelsWithSuffixAndField != null ? DOT.split(detectPassthroughModelsWithSuffixAndField)[1] : null; } public boolean getInferUniqueIdFromNameSuffix() { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptNestjsClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptNestjsClientCodegen.java index cfd075645e4c..854e702f7a72 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptNestjsClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptNestjsClientCodegen.java @@ -41,8 +41,8 @@ public class TypeScriptNestjsClientCodegen extends AbstractTypeScriptClientCodegen { private final Logger LOGGER = LoggerFactory.getLogger(TypeScriptNestjsClientCodegen.class); - private static String CLASS_NAME_SUFFIX_PATTERN = "^[a-zA-Z0-9]*$"; - private static String FILE_NAME_SUFFIX_PATTERN = "^[a-zA-Z0-9.-]*$"; + private static final java.util.regex.Pattern CLASS_NAME_SUFFIX_PATTERN = java.util.regex.Pattern.compile("^[a-zA-Z0-9]*$"); + private static final java.util.regex.Pattern FILE_NAME_SUFFIX_PATTERN = java.util.regex.Pattern.compile("^[a-zA-Z0-9.-]*$"); public static final String NPM_REPOSITORY = "npmRepository"; public static final String WITH_INTERFACES = "withInterfaces"; @@ -496,7 +496,7 @@ public String removeModelPrefixSuffix(String name) { * @param value The value that is being validated. */ private void validateFileSuffixArgument(String argument, String value) { - if (!value.matches(FILE_NAME_SUFFIX_PATTERN)) { + if (!FILE_NAME_SUFFIX_PATTERN.matcher(value).matches()) { throw new IllegalArgumentException( String.format(Locale.ROOT, "%s file suffix only allows '.', '-' and alphanumeric characters.", argument) ); @@ -511,7 +511,7 @@ private void validateFileSuffixArgument(String argument, String value) { * @param value The value that is being validated. */ private void validateClassSuffixArgument(String argument, String value) { - if (!value.matches(CLASS_NAME_SUFFIX_PATTERN)) { + if (!CLASS_NAME_SUFFIX_PATTERN.matcher(value).matches()) { throw new IllegalArgumentException( String.format(Locale.ROOT, "%s class suffix only allows alphanumeric characters.", argument) ); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptNestjsServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptNestjsServerCodegen.java index 8e8a9a2fdc1d..7e9fa789d005 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptNestjsServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptNestjsServerCodegen.java @@ -42,8 +42,9 @@ public class TypeScriptNestjsServerCodegen extends AbstractTypeScriptClientCodegen { private final Logger LOGGER = LoggerFactory.getLogger(TypeScriptNestjsServerCodegen.class); - private static String CLASS_NAME_SUFFIX_PATTERN = "^[a-zA-Z0-9]*$"; - private static String FILE_NAME_SUFFIX_PATTERN = "^[a-zA-Z0-9.-]*$"; + private static final java.util.regex.Pattern CLASS_NAME_SUFFIX_PATTERN = java.util.regex.Pattern.compile("^[a-zA-Z0-9]*$"); + private static final java.util.regex.Pattern FILE_NAME_SUFFIX_PATTERN = java.util.regex.Pattern.compile("^[a-zA-Z0-9.-]*$"); + private static final java.util.regex.Pattern QUOTED_STRING = java.util.regex.Pattern.compile("([\"'].*[\"'])"); public static final String NPM_REPOSITORY = "npmRepository"; public static final String TAGGED_UNIONS = "taggedUnions"; @@ -291,7 +292,7 @@ private boolean isLanguagePrimitive(String type) { private boolean isInlineUnion(String type) { return Arrays.stream(type.split("\\|")) .map(String::trim) - .allMatch(value -> value.matches("([\"'].*[\"'])")); + .allMatch(value -> QUOTED_STRING.matcher(value).matches()); } private boolean isLanguageGenericType(String type) { @@ -322,7 +323,7 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap operations, L List operationList = objectMap.getOperation(); for (CodegenOperation operation : operationList) { - operation.path = operation.path.replaceAll("\\{([^}]+)}", ":$1"); + operation.path = PATH_PARAMETER.matcher(operation.path).replaceAll(":$1"); operation.httpMethod = camelize(operation.httpMethod.toLowerCase(Locale.ROOT)); List params = operation.allParams; @@ -561,7 +562,7 @@ public String removeModelPrefixSuffix(String name) { * @param value The value that is being validated. */ private void validateFileSuffixArgument(String argument, String value) { - if (value != null && !value.matches(FILE_NAME_SUFFIX_PATTERN)) { + if (value != null && !FILE_NAME_SUFFIX_PATTERN.matcher(value).matches()) { throw new IllegalArgumentException( String.format(Locale.ROOT, "%s file suffix only allows '.', '-' and alphanumeric characters.", argument) ); @@ -576,7 +577,7 @@ private void validateFileSuffixArgument(String argument, String value) { * @param value The value that is being validated. */ private void validateClassSuffixArgument(String argument, String value) { - if (value != null && !value.matches(CLASS_NAME_SUFFIX_PATTERN)) { + if (value != null && !CLASS_NAME_SUFFIX_PATTERN.matcher(value).matches()) { throw new IllegalArgumentException( String.format(Locale.ROOT, "%s class suffix only allows alphanumeric characters.", argument) ); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/WsdlSchemaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/WsdlSchemaCodegen.java index ae4400a5acdb..18f360b3a9ac 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/WsdlSchemaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/WsdlSchemaCodegen.java @@ -25,6 +25,7 @@ import org.openapitools.codegen.model.ModelMap; import org.openapitools.codegen.model.ModelsMap; import org.openapitools.codegen.model.OperationsMap; +import org.openapitools.codegen.utils.PatternCache; import java.io.File; import java.text.Normalizer; @@ -107,14 +108,14 @@ public void processOpts() { private String escapeTitle(String title) { // strip umlauts etc. - final String normalizedTitle = Normalizer.normalize(title, Normalizer.Form.NFD) - .replaceAll("[^\\p{ASCII}]", ""); + final String normalizedTitle = PatternCache.get("[^\\p{ASCII}]").matcher(Normalizer.normalize(title, Normalizer.Form.NFD)) + .replaceAll(""); return super.escapeUnsafeCharacters(normalizedTitle); } public String processOpenapiSpecDescription(String description) { if (description != null) { - return description.replaceAll("\\s+", " "); + return WHITESPACE.matcher(description).replaceAll(" "); } else { return "No description provided"; } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/XojoClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/XojoClientCodegen.java index 2a057ce0ef40..532859bd85d6 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/XojoClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/XojoClientCodegen.java @@ -432,7 +432,7 @@ public String toOperationId(String operationId) { } // operationId starts with a number - if (operationId.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(operationId).matches()) { LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, camelize(sanitizeName("call_" + operationId), LOWERCASE_FIRST_LETTER)); operationId = camelize(sanitizeName("Call_" + operationId)); } @@ -476,7 +476,7 @@ public String toEnumVarName(String name, String datatype) { // Camelize only when we have a structure defined below boolean camelized = false; - if (name.matches("[A-Z][a-z0-9]+[a-zA-Z0-9]*")) { + if (CAMEL_INITIAL.matcher(name).matches()) { name = camelize(name); camelized = true; } @@ -495,8 +495,8 @@ public String toEnumVarName(String name, String datatype) { } char[] separators = {'-', '_', ' ', ':', '(', ')'}; - return camelize(replaceSpecialCharacters(WordUtils.capitalizeFully(StringUtils.lowerCase(name), separators) - .replaceAll("[-_ :\\(\\)]", ""))); + return camelize(replaceSpecialCharacters(DASH_UNDERSCORE_SPACE_COLON_PARENTHESES.matcher(WordUtils.capitalizeFully(StringUtils.lowerCase(name), separators)) + .replaceAll(""))); } private String replaceSpecialCharacters(String name) { @@ -510,7 +510,7 @@ private String replaceSpecialCharacters(String name) { } // Fallback, replace unknowns with underscore. - name = name.replaceAll("\\W+", "_"); + name = NON_WORD_PLUS.matcher(name).replaceAll("_"); return name; } @@ -547,12 +547,12 @@ public String toVarName(String name) { name = sanitizeName(name); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } // for reserved word or word starting with number, append _ - if (isReservedWord(name) || name.matches("^\\d.*")) { + if (isReservedWord(name) || STARTS_WITH_DIGIT.matcher(name).matches()) { name = escapeReservedWord(name); } return name; @@ -564,10 +564,10 @@ public String toParamName(String name) { name = sanitizeName(name); // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); + name = MINUS.matcher(name).replaceAll("_"); // if it's all upper case, do nothing - if (name.matches("^[A-Z_]*$")) { + if (ALL_UPPER_UNDERSCORE.matcher(name).matches()) { return name; } @@ -581,7 +581,7 @@ public String toParamName(String name) { } // for words starting with number, prepend _ - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { name = "_" + name; } @@ -595,14 +595,14 @@ public String toModelFilename(String name) { name = sanitizeName(name); // replace - with _ e.g. created-at => created_at - name = name.replaceAll("-", "_"); + name = MINUS.matcher(name).replaceAll("_"); if (isReservedWord(name)) { name = escapeReservedWord(name); } // for words starting with number, prepend A - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { name = "A" + name; } @@ -637,7 +637,7 @@ public String toModelName(String name) { } // model name starts with number - if (name.matches("^\\d.*")) { + if (STARTS_WITH_DIGIT.matcher(name).matches()) { // e.g. 200Response => Model200Response (after camelize) String modelName = "Model" + name; LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", name, diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/mustache/KebabCaseLambda.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/mustache/KebabCaseLambda.java index af384969debc..00bd67a95cd6 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/mustache/KebabCaseLambda.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/mustache/KebabCaseLambda.java @@ -36,21 +36,25 @@ * {{#kebabcase}}{{summary}}{{/kebabcase}} * */ +import java.util.regex.Pattern; + public class KebabCaseLambda implements Mustache.Lambda { + + private static final Pattern PACKAGE_SEPARATOR = Pattern.compile("\\."); + private static final Pattern SPECIAL = Pattern.compile("[^A-Za-z0-9_]"); + private static final Pattern FIRST_PATTERN = Pattern.compile("([A-Z]+)([A-Z][a-z])"); + private static final Pattern SECOND_PATTERN = Pattern.compile("([a-z\\d])([A-Z])"); + + private static final String SPACE_REPLACEMENT = "$1 $2"; + @Override public void execute(Template.Fragment fragment, Writer writer) throws IOException { String text = fragment.execute(); - String SPECIAL = "[^A-Za-z0-9_]"; - String firstPattern = "([A-Z]+)([A-Z][a-z])"; - String secondPattern = "([a-z\\d])([A-Z])"; - String replacementPattern = "$1 $2"; - // Replace package separator with slash. - text = text.replaceAll("\\.", "/"); - text = text.replaceAll(SPECIAL, ""); - // Replace capital letter with _ plus lowercase letter. - text = text.replaceAll(firstPattern, replacementPattern); - text = text.replaceAll(secondPattern, replacementPattern); + text = PACKAGE_SEPARATOR.matcher(text).replaceAll("/"); + text = SPECIAL.matcher(text).replaceAll(""); + text = FIRST_PATTERN.matcher(text).replaceAll(SPACE_REPLACEMENT); + text = SECOND_PATTERN.matcher(text).replaceAll(SPACE_REPLACEMENT); text = text.replace('_', '-'); text = text.replace(' ', '-'); text = text.toLowerCase(Locale.ROOT); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java index 3e355713e7f9..95a88ee7566a 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/ModelUtils.java @@ -2017,7 +2017,7 @@ private static ObjectMapper getRightMapper(String data) { */ public static JsonNode readWithInfo(String location, List auths) throws Exception { String data; - location = location.replaceAll("\\\\", "/"); + location = location.replace("\\", "/"); if (location.toLowerCase(Locale.ROOT).startsWith("http")) { data = RemoteUrl.urlToString(location, auths); } else { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/PatternCache.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/PatternCache.java new file mode 100644 index 000000000000..fa68699129ae --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/PatternCache.java @@ -0,0 +1,92 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.codegen.utils; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; + +import java.util.regex.Pattern; + +/** + * A shared, process-wide cache of compiled {@link Pattern} objects, keyed by regex string + * (and optional flags). + * + *

Motivation

+ * {@link Pattern#compile(String)} is non-trivial: it parses and compiles the regex into an + * internal automaton. When the same literal regex is compiled inside a hot loop (e.g. once + * per property name, per operation, or per model), the overhead accumulates. This cache + * ensures each distinct {@code (regex, flags)} pair is compiled at most once. + * + *

Thread safety

+ * Backed by a Caffeine cache; all operations are thread-safe. Caffeine's internal striped + * locking means concurrent requests for the same key result in only one compilation. + * + *

Memory

+ * The cache is bounded at {@value #MAX_SIZE} entries via LRU eviction, which is effectively + * unlimited for any single codegen run (realistic specs have at most dozens of distinct + * patterns) while protecting long-lived server processes (e.g. openapi-generator-online) + * from unbounded growth caused by user-supplied {@code pattern:} values. + * + *

What goes here vs. {@code static final Pattern}

+ * Use this cache only for dynamic pattern keys — i.e. patterns whose regex string + * is not known at compile time (user config, OpenAPI {@code pattern:} field values, etc.). + * Fixed literal patterns that appear in a single class should remain + * {@code private static final Pattern} constants; they are resolved at class-load time and + * accessed via a direct field read, which is faster than any map lookup. + */ +public final class PatternCache { + + /** Maximum number of cached entries before LRU eviction kicks in. */ + static final int MAX_SIZE = 10_000; + + private static final Cache CACHE = Caffeine.newBuilder() + .maximumSize(MAX_SIZE) + .build(); + + private PatternCache() { + // utility class – no instances + } + + /** + * Returns a compiled {@link Pattern} for {@code regex} with no flags, compiling it on the + * first call and returning the cached instance on subsequent calls. + * + * @param regex the regular-expression string (must not be {@code null}) + * @return a compiled {@link Pattern} + */ + public static Pattern get(String regex) { + return CACHE.get(regex, Pattern::compile); + } + + /** + * Returns a compiled {@link Pattern} for {@code regex} with the given {@code flags}, + * compiling it on the first call and returning the cached instance on subsequent calls. + * + *

The cache key encodes both the regex string and the flags value so that the same + * regex compiled with different flags (e.g. {@link Pattern#UNICODE_CHARACTER_CLASS}) + * is stored as a separate entry. + * + * @param regex the regular-expression string (must not be {@code null}) + * @param flags match flags, as accepted by {@link Pattern#compile(String, int)} + * @return a compiled {@link Pattern} + */ + public static Pattern get(String regex, int flags) { + // NUL character as separator is safe because it cannot appear in a valid regex string. + return CACHE.get(regex + '\0' + flags, k -> Pattern.compile(regex, flags)); + } +} + diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/SemVer.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/SemVer.java index 0dc350677794..0ecddd4b1fa9 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/SemVer.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/SemVer.java @@ -17,14 +17,18 @@ package org.openapitools.codegen.utils; +import java.util.regex.Pattern; + public class SemVer implements Comparable { + private static final Pattern DOT = Pattern.compile("\\."); + public final int major; public final int minor; public final int revision; public SemVer(String versionString) { - String[] tokens = versionString.split("\\."); + String[] tokens = DOT.split(versionString); major = Integer.parseInt(tokens[0]); minor = tokens.length < 2 ? 0 : Integer.parseInt(tokens[1]); revision = tokens.length < 3 ? 0 : Integer.parseInt(tokens[2]); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/StringUtils.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/StringUtils.java index 18517bed319e..b043a77fc3bb 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/StringUtils.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/StringUtils.java @@ -56,10 +56,10 @@ public class StringUtils { .build(); } - private static Pattern capitalLetterPattern = Pattern.compile("([A-Z]+)([A-Z][a-z][a-z]+)"); - private static Pattern lowercasePattern = Pattern.compile("([a-z\\d])([A-Z])"); - private static Pattern pkgSeparatorPattern = Pattern.compile("\\."); - private static Pattern dollarPattern = Pattern.compile("\\$"); + private static final Pattern CAPITAL_LETTER_PATTERN = Pattern.compile("([A-Z]+)([A-Z][a-z][a-z]+)"); + private static final Pattern LOWERCASE_PATTERN = Pattern.compile("([a-z\\d])([A-Z])"); + private static final Pattern PKG_SEPARATOR_PATTERN = Pattern.compile("\\."); + private static final Pattern DOLLAR_PATTERN = Pattern.compile("\\$"); /** * Underscore the given word. @@ -74,12 +74,12 @@ public static String underscore(final String word) { String result; String replacementPattern = "$1_$2"; // Replace package separator with slash. - result = pkgSeparatorPattern.matcher(wordToUnderscore).replaceAll("/"); + result = PKG_SEPARATOR_PATTERN.matcher(wordToUnderscore).replaceAll("/"); // Replace $ with two underscores for inner classes. - result = dollarPattern.matcher(result).replaceAll("__"); + result = DOLLAR_PATTERN.matcher(result).replaceAll("__"); // Replace capital letter with _ plus lowercase letter. - result = capitalLetterPattern.matcher(result).replaceAll(replacementPattern); - result = lowercasePattern.matcher(result).replaceAll(replacementPattern); + result = CAPITAL_LETTER_PATTERN.matcher(result).replaceAll(replacementPattern); + result = LOWERCASE_PATTERN.matcher(result).replaceAll(replacementPattern); result = result.replace('-', '_'); // replace space with underscore result = result.replace(' ', '_'); @@ -88,6 +88,7 @@ public static String underscore(final String word) { }); } + private static final Pattern UNDERSCORE_OR_SPACE = Pattern.compile("[_ ]+"); /** * Dashize the given word. * @@ -95,7 +96,7 @@ public static String underscore(final String word) { * @return The dashized version of the word, e.g. "my-name" */ public static String dashize(String word) { - return underscore(word).replaceAll("[_ ]+", "-"); + return UNDERSCORE_OR_SPACE.matcher(underscore(word)).replaceAll("-"); } /** @@ -110,12 +111,13 @@ public static String camelize(String word) { return camelize(word, UPPERCASE_FIRST_CHAR); } - private static Pattern camelizeSlashPattern = Pattern.compile("\\/(.?)"); - private static Pattern camelizeUppercasePattern = Pattern.compile("(\\.?)(\\w)([^\\.]*)$"); - private static Pattern camelizeUnderscorePattern = Pattern.compile("(_)(.)"); - private static Pattern camelizeHyphenPattern = Pattern.compile("(-)(.)"); - private static Pattern camelizeDollarPattern = Pattern.compile("\\$"); - private static Pattern camelizeSimpleUnderscorePattern = Pattern.compile("_"); + private static final Pattern CAMELIZE_SLASH_PATTERN = Pattern.compile("/(.?)"); + private static final Pattern CAMELIZE_UPPERCASE_PATTERN = Pattern.compile("(\\.?)(\\w)([^.]*)$"); + private static final Pattern CAMELIZE_UNDERSCORE_PATTERN = Pattern.compile("(_)(.)"); + private static final Pattern CAMELIZE_HYPHEN_PATTERN = Pattern.compile("(-)(.)"); + private static final Pattern CAMELIZE_DOLLAR_PATTERN = Pattern.compile("\\$"); + private static final Pattern CAMELIZE_SIMPLE_UNDERSCORE_PATTERN = Pattern.compile("_"); + private static final Pattern DOT = Pattern.compile("\\."); /** * Camelize name (parameter, property, method, etc) @@ -131,14 +133,14 @@ public static String camelize(final String inputWord, CamelizeOption camelizeOpt String word = pair.getKey(); CamelizeOption option = pair.getValue(); // Replace all slashes with dots (package separator) - Matcher m = camelizeSlashPattern.matcher(word); + Matcher m = CAMELIZE_SLASH_PATTERN.matcher(word); while (m.find()) { word = m.replaceFirst("." + m.group(1).replace("\\", "\\\\")/*.toUpperCase()*/); - m = camelizeSlashPattern.matcher(word); + m = CAMELIZE_SLASH_PATTERN.matcher(word); } // case out dots - String[] parts = word.split("\\."); + String[] parts = DOT.split(word); StringBuilder f = new StringBuilder(); for (String z : parts) { if (z.length() > 0) { @@ -147,38 +149,38 @@ public static String camelize(final String inputWord, CamelizeOption camelizeOpt } word = f.toString(); - m = camelizeSlashPattern.matcher(word); + m = CAMELIZE_SLASH_PATTERN.matcher(word); while (m.find()) { word = m.replaceFirst(Character.toUpperCase(m.group(1).charAt(0)) + m.group(1).substring(1)/*.toUpperCase()*/); - m = camelizeSlashPattern.matcher(word); + m = CAMELIZE_SLASH_PATTERN.matcher(word); } // Uppercase the class name. - m = camelizeUppercasePattern.matcher(word); + m = CAMELIZE_UPPERCASE_PATTERN.matcher(word); if (m.find()) { String rep = m.group(1) + m.group(2).toUpperCase(Locale.ROOT) + m.group(3); - rep = camelizeDollarPattern.matcher(rep).replaceAll("\\\\\\$"); + rep = CAMELIZE_DOLLAR_PATTERN.matcher(rep).replaceAll("\\\\\\$"); word = m.replaceAll(rep); } // Remove all underscores (underscore_case to camelCase) - m = camelizeUnderscorePattern.matcher(word); + m = CAMELIZE_UNDERSCORE_PATTERN.matcher(word); while (m.find()) { String original = m.group(2); String upperCase = original.toUpperCase(Locale.ROOT); if (original.equals(upperCase)) { - word = camelizeSimpleUnderscorePattern.matcher(word).replaceFirst(""); + word = CAMELIZE_SIMPLE_UNDERSCORE_PATTERN.matcher(word).replaceFirst(""); } else { word = m.replaceFirst(upperCase); } - m = camelizeUnderscorePattern.matcher(word); + m = CAMELIZE_UNDERSCORE_PATTERN.matcher(word); } // Remove all hyphens (hyphen-case to camelCase) - m = camelizeHyphenPattern.matcher(word); + m = CAMELIZE_HYPHEN_PATTERN.matcher(word); while (m.find()) { word = m.replaceFirst(m.group(2).toUpperCase(Locale.ROOT)); - m = camelizeHyphenPattern.matcher(word); + m = CAMELIZE_HYPHEN_PATTERN.matcher(word); } switch (option) { @@ -191,7 +193,7 @@ public static String camelize(final String inputWord, CamelizeOption camelizeOpt } // remove all underscore - word = camelizeSimpleUnderscorePattern.matcher(word).replaceAll(""); + word = CAMELIZE_SIMPLE_UNDERSCORE_PATTERN.matcher(word).replaceAll(""); return word; }); } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/URLPathUtils.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/URLPathUtils.java index 47c98f557411..a7bb6460eba7 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/URLPathUtils.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/URLPathUtils.java @@ -38,7 +38,12 @@ public class URLPathUtils { private static final Logger LOGGER = LoggerFactory.getLogger(URLPathUtils.class); public static final String LOCAL_HOST = "http://localhost"; - public static final Pattern VARIABLE_PATTERN = Pattern.compile("(? userDefinedVariables) { @@ -210,7 +215,7 @@ private static String sanitizeUrl(String url) { } else if (url.startsWith("/")) { url = LOCAL_HOST + url; once(LOGGER).info("'host' (OAS 2.0) or 'servers' (OAS 3.0) not defined in the spec. Default to [{}] for server URL [{}]", LOCAL_HOST, url); - } else if (!url.matches("[a-zA-Z][0-9a-zA-Z.+\\-]+://.+")) { + } else if (!URL_WITH_SCHEME.matcher(url).matches()) { // Add http scheme for urls without a scheme. // 2.0 spec is restricted to the following schemes: "http", "https", "ws", "wss" // 3.0 spec does not have an enumerated list of schemes @@ -235,7 +240,7 @@ private static URL getDefaultUrl() { public static boolean isRelativeUrl(List servers) { if (servers != null && servers.size() > 0) { final Server firstServer = servers.get(0); - return Pattern.matches("^(\\/[\\w\\d.~@-]+)+", firstServer.getUrl()); + return RELATIVE_PATH_URL.matcher(firstServer.getUrl()).matches(); } return false; } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/utils/PatternCacheTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/utils/PatternCacheTest.java new file mode 100644 index 000000000000..b46b82732b57 --- /dev/null +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/utils/PatternCacheTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.codegen.utils; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.regex.Pattern; + +public class PatternCacheTest { + + @Test + public void testSameInstanceReturnedForSameRegex() { + Pattern first = PatternCache.get("\\W+"); + Pattern second = PatternCache.get("\\W+"); + Assert.assertSame(first, second, + "PatternCache must return the same Pattern instance for the same regex string"); + } + + @Test + public void testSameInstanceReturnedForSameRegexAndFlags() { + Pattern first = PatternCache.get("\\W+", Pattern.UNICODE_CHARACTER_CLASS); + Pattern second = PatternCache.get("\\W+", Pattern.UNICODE_CHARACTER_CLASS); + Assert.assertSame(first, second, + "PatternCache must return the same Pattern instance for identical (regex, flags) pairs"); + } + + @Test + public void testDifferentInstanceForDifferentFlags() { + Pattern noFlags = PatternCache.get("\\W+"); + Pattern withFlags = PatternCache.get("\\W+", Pattern.UNICODE_CHARACTER_CLASS); + Assert.assertNotSame(noFlags, withFlags, + "PatternCache must store separate entries for the same regex compiled with different flags"); + Assert.assertEquals(noFlags.flags() & Pattern.UNICODE_CHARACTER_CLASS, 0, + "Pattern compiled without flags must not have UNICODE_CHARACTER_CLASS set"); + Assert.assertEquals(withFlags.flags() & Pattern.UNICODE_CHARACTER_CLASS, Pattern.UNICODE_CHARACTER_CLASS, + "Pattern compiled with UNICODE_CHARACTER_CLASS must have that flag set " + + "(note: the JVM may also implicitly set UNICODE_CASE=64, so exact equality is not checked)"); + } + + @Test + public void testDifferentInstanceForDifferentRegex() { + Pattern a = PatternCache.get("\\d+"); + Pattern b = PatternCache.get("\\W+"); + Assert.assertNotSame(a, b, + "PatternCache must store separate entries for different regex strings"); + } + + @Test + public void testPatternFunctionallyCorrect() { + Pattern pattern = PatternCache.get("\\d+"); + Assert.assertTrue(pattern.matcher("123").matches()); + Assert.assertFalse(pattern.matcher("abc").matches()); + } + + @Test + public void testConcurrentAccessReturnsSameInstance() throws Exception { + final String regex = "concurrent-test-[a-z]+"; + final int threads = 20; + + ExecutorService executor = Executors.newFixedThreadPool(threads); + List> tasks = new ArrayList<>(threads); + for (int i = 0; i < threads; i++) { + tasks.add(() -> PatternCache.get(regex)); + } + + List> futures = executor.invokeAll(tasks); + executor.shutdown(); + + Pattern expected = futures.get(0).get(); + for (Future future : futures) { + Assert.assertSame(future.get(), expected, + "All concurrent callers must receive the same cached Pattern instance"); + } + } + + @Test + public void testMaxSizeIsReasonable() { + Assert.assertTrue(PatternCache.MAX_SIZE >= 1_000, + "PatternCache.MAX_SIZE should be at least 1000 to cover realistic workloads"); + } +} + +