[jOOQ/jOOQ#18473] Slash in name results in invalid identifier being generated using KotlinGenerator

This includes:

- [jOOQ/jOOQ#18641] Bad file names generated in KotlinGenerator and ScalaGenerator when using special characters
- [jOOQ/jOOQ#18643] KotlinGenerator produces bad code when table / column identifier disambiguation clashes with special character escaping
- [jOOQ/jOOQ#18654] Compilation error in Kotlin generated code when table name contains special characters and paths are generated
This commit is contained in:
Lukas Eder 2025-06-18 16:37:52 +02:00
parent cb049ee619
commit 74c4bf95d5
3 changed files with 49 additions and 27 deletions

View File

@ -395,7 +395,13 @@ class GenerationUtil {
// case '}':
case '<':
case '>':
// [#18641] OS forbidden characters: https://stackoverflow.com/q/1976007/521799
case '*':
case '?':
case '|':
return false;
default:
return true;
}
@ -475,19 +481,26 @@ class GenerationUtil {
else if (language == KOTLIN && !isKotlinIdentifierPart(c))
sb.append(escape(c));
// TODO: Should we do this for Scala as well?
else if (language == KOTLIN && !Character.isJavaIdentifierPart(c))
return "`" + literal + "`";
else if (language == KOTLIN && i == 0 && !Character.isJavaIdentifierStart(c))
return "`" + literal + "`";
// [#10867] The $ character is not allowed in Kotlin unquoted identifiers
else if (language == KOTLIN && c == '$')
return "`" + literal + "`";
else
sb.append(c);
}
// TODO: Should we do this for Scala as well?
if (language == KOTLIN) {
for (int i = 0; i < sb.length(); i++) {
char c = sb.charAt(i);
if (!Character.isJavaIdentifierPart(c))
return "`" + sb + "`";
else if (i == 0 && !Character.isJavaIdentifierStart(c))
return "`" + sb + "`";
// [#10867] The $ character is not allowed in Kotlin unquoted identifiers
else if (c == '$')
return "`" + sb + "`";
}
}
return sb.toString();
}
@ -499,7 +512,7 @@ class GenerationUtil {
return convertToIdentifier(literal, Language.JAVA);
}
private static String escape(char c) {
static String escape(char c) {
if (c == ' ' || c == '-' || c == '.')
return "_";
else

View File

@ -51,6 +51,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import org.jooq.Record;
import org.jooq.impl.AbstractRoutine;
@ -118,18 +119,18 @@ class GeneratorStrategyWrapper extends AbstractDelegatingGeneratorStrategy {
TypedElementDefinition<?> e = (TypedElementDefinition<?>) definition;
if (identifier.equals(getJavaIdentifier(e.getContainer())))
return identifier + "_";
return append(identifier, "_");
// [#2781] Disambiguate collisions with the leading package name
if (identifier.equals(getJavaPackageName(e.getContainer()).replaceAll("\\..*", "")))
return identifier + "_";
return append(identifier, "_");
}
else if (definition instanceof TableDefinition) {
SchemaDefinition schema = definition.getSchema();
if (identifier.equals(getJavaIdentifier(schema)))
return identifier + "_";
return append(identifier, "_");
}
// [#5557] Once more, this causes issues...
@ -137,13 +138,27 @@ class GeneratorStrategyWrapper extends AbstractDelegatingGeneratorStrategy {
CatalogDefinition catalog = definition.getCatalog();
if (identifier.equals(getJavaIdentifier(catalog)))
return identifier + "_";
return append(identifier, "_");
}
identifier = overload(definition, Mode.DEFAULT, identifier);
return identifier;
}
static final String append(String identifier, String suffix) {
if (identifier.startsWith("`") && identifier.endsWith("`"))
return identifier.replaceFirst("^`(.*)`$", "`$1" + Matcher.quoteReplacement(suffix) + "`");
else
return identifier + suffix;
}
static final String prepend(String prefix, String identifier) {
if (identifier.startsWith("`") && identifier.endsWith("`"))
return identifier.replaceFirst("^`(.*)`$", "`" + Matcher.quoteReplacement(prefix) + "$1`");
else
return prefix + identifier;
}
@Override
public String getJavaSetterName(Definition definition, Mode mode) {
return fixMethodName(definition, mode, delegate.getJavaSetterName(definition, mode));
@ -171,7 +186,7 @@ class GeneratorStrategyWrapper extends AbstractDelegatingGeneratorStrategy {
*/
private String overload(Definition definition, Mode mode, String identifier) {
if (!StringUtils.isBlank(definition.getOverload()))
identifier += getOverloadSuffix(definition, mode, definition.getOverload());
identifier = append(identifier, getOverloadSuffix(definition, mode, definition.getOverload()));
return identifier;
}

View File

@ -7996,9 +7996,6 @@ public class JavaGenerator extends AbstractGenerator {
? out.ref(getStrategy().getFullJavaIdentifier(foreignKey))
: out.ref(getStrategy().getFullJavaIdentifier(foreignKey), 2);
// [#13008] Prevent conflicts with the below leading underscore
final String unquotedKeyMethodName = keyMethodName.replace("`", "");
keyMethodNames.put(keyMethodName, foreignKey);
if (scala || kotlin) {}
@ -8022,8 +8019,8 @@ public class JavaGenerator extends AbstractGenerator {
out.println("%s%sval %s: %s by lazy { %s(this, %s, null) }", visibility(), keyMethodOverride ? "override " : "", keyMethodName, referencedTableClassName, referencedTableClassName, keyFullId);
}
else {
out.println("%s%sfun %s(): %s = _%s", visibility(), keyMethodOverride ? "override " : "", keyMethodName, referencedTableClassName, unquotedKeyMethodName);
out.println("private val _%s: %s by lazy { %s(this, %s, null) }", unquotedKeyMethodName, referencedTableClassName, referencedTableClassName, keyFullId);
out.println("%s%sfun %s(): %s = %s", visibility(), keyMethodOverride ? "override " : "", keyMethodName, referencedTableClassName, GeneratorStrategyWrapper.prepend("_", keyMethodName));
out.println("private val %s: %s by lazy { %s(this, %s, null) }", GeneratorStrategyWrapper.prepend("_", keyMethodName), referencedTableClassName, referencedTableClassName, keyFullId);
}
}
else {
@ -8070,15 +8067,12 @@ public class JavaGenerator extends AbstractGenerator {
+ (generateImplicitJoinPathTableSubtypes() ? ("." + getStrategy().getJavaClassName(foreignKey.getReferencingTable(), Mode.PATH)) : "")
);
// [#13008] Prevent conflicts with the below leading underscore
final String unquotedKeyMethodName = keyMethodName.replace("`", "");
if (scala) {}
else {
out.println();
if (kotlin)
out.println("private lateinit var _%s: %s", unquotedKeyMethodName, referencingTableClassName);
out.println("private lateinit var %s: %s", GeneratorStrategyWrapper.prepend("_", keyMethodName), referencingTableClassName);
else
out.println("private transient %s _%s;", referencingTableClassName, keyMethodName);
}
@ -8093,10 +8087,10 @@ public class JavaGenerator extends AbstractGenerator {
}
else if (kotlin) {
out.println("%s%sfun %s(): %s {", visibility(), keyMethodOverride ? "override " : "", keyMethodName, referencingTableClassName);
out.println("if (!this::_%s.isInitialized)", unquotedKeyMethodName);
out.println("_%s = %s(this, null, %s.inverseKey)", unquotedKeyMethodName, referencingTableClassName, keyFullId);
out.println("if (!this::%s.isInitialized)", GeneratorStrategyWrapper.prepend("_", keyMethodName));
out.println("%s = %s(this, null, %s.inverseKey)", GeneratorStrategyWrapper.prepend("_", keyMethodName), referencingTableClassName, keyFullId);
out.println();
out.println("return _%s;", unquotedKeyMethodName);
out.println("return %s;", GeneratorStrategyWrapper.prepend("_", keyMethodName));
out.println("}");
if (generateImplicitJoinPathsAsKotlinProperties()) {