diff --git a/jOOQ-codegen/src/main/java/org/jooq/util/GenerationUtil.java b/jOOQ-codegen/src/main/java/org/jooq/util/GenerationUtil.java index 34259901ed..2d3e099188 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/util/GenerationUtil.java +++ b/jOOQ-codegen/src/main/java/org/jooq/util/GenerationUtil.java @@ -39,9 +39,12 @@ import static java.util.Collections.unmodifiableSet; import static org.jooq.impl.DSL.name; import static org.jooq.util.AbstractGenerator.Language.JAVA; import static org.jooq.util.AbstractGenerator.Language.SCALA; +import static org.jooq.util.GenerationUtil.ExpressionType.CONSTRUCTOR_REFERENCE; +import static org.jooq.util.GenerationUtil.ExpressionType.EXPRESSION; import java.util.HashSet; import java.util.Set; +import java.util.regex.Pattern; import org.jooq.Name; import org.jooq.SQLDialect; @@ -55,6 +58,9 @@ import org.jooq.util.h2.H2DataType; */ class GenerationUtil { + static final Pattern TYPE_REFERENCE_PATTERN = Pattern.compile("^((?:[\\p{L}_$][\\p{L}\\p{N}_$]*\\.)*[\\p{L}_$][\\p{L}\\p{N}_$]*)((?:<.*>|\\[.*\\])*)$"); + static final Pattern PLAIN_GENERIC_TYPE_PATTERN = Pattern.compile("[<\\[]((?:[\\p{L}_$][\\p{L}\\p{N}_$]*\\.)*[\\p{L}_$][\\p{L}\\p{N}_$]*)[>\\]]"); + private static Set JAVA_KEYWORDS = unmodifiableSet(new HashSet(asList( "abstract", "assert", @@ -414,4 +420,16 @@ class GenerationUtil { return result; } + + static ExpressionType expressionType(String expression) { + if (TYPE_REFERENCE_PATTERN.matcher(expression).matches()) + return CONSTRUCTOR_REFERENCE; + else + return EXPRESSION; + } + + enum ExpressionType { + CONSTRUCTOR_REFERENCE, + EXPRESSION + } } diff --git a/jOOQ-codegen/src/main/java/org/jooq/util/JavaGenerator.java b/jOOQ-codegen/src/main/java/org/jooq/util/JavaGenerator.java index aa0049c2a2..3be2daf9cf 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/util/JavaGenerator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/util/JavaGenerator.java @@ -1660,19 +1660,17 @@ public class JavaGenerator extends AbstractGenerator { final String attrId = out.ref(getStrategy().getJavaIdentifier(attribute), 2); final String attrName = attribute.getName(); final String attrComment = StringUtils.defaultString(attribute.getComment()); - final List converters = out.ref(list( - attribute.getType().getConverter(), - attribute.getType().getBinding() - )); + final List converter = out.ref(list(attribute.getType().getConverter())); + final List binding = out.ref(list(attribute.getType().getBinding())); if (scala) { - out.tab(1).println("private val %s : %s[%s, %s] = %s.createField(\"%s\", %s, this, \"%s\"[[before=, ][new %s]])", - attrId, UDTField.class, recordType, attrType, UDTImpl.class, attrName, attrTypeRef, escapeString(""), converters); + out.tab(1).println("private val %s : %s[%s, %s] = %s.createField(\"%s\", %s, this, \"%s\"" + converterTemplate(converter) + converterTemplate(binding) + ")", + attrId, UDTField.class, recordType, attrType, UDTImpl.class, attrName, attrTypeRef, escapeString(""), converter, binding); } else { out.tab(1).javadoc("The attribute %s.%s", attribute.getQualifiedOutputName(), defaultIfBlank(" " + attrComment, "")); - out.tab(1).println("public static final %s<%s, %s> %s = createField(\"%s\", %s, %s, \"%s\"[[before=, ][new %s()]]);", - UDTField.class, recordType, attrType, attrId, attrName, attrTypeRef, udtId, escapeString(""), converters); + out.tab(1).println("public static final %s<%s, %s> %s = createField(\"%s\", %s, %s, \"%s\"" + converterTemplate(converter) + converterTemplate(binding) + ");", + UDTField.class, recordType, attrType, attrId, attrName, attrTypeRef, udtId, escapeString(""), converter, binding); } } @@ -1995,8 +1993,6 @@ public class JavaGenerator extends AbstractGenerator { - - @@ -3325,23 +3321,21 @@ public class JavaGenerator extends AbstractGenerator { final String columnId = out.ref(getStrategy().getJavaIdentifier(column), colRefSegments(column)); final String columnName = column.getName(); final String columnComment = StringUtils.defaultString(column.getComment()); - final List converters = out.ref(list( - column.getType().getConverter(), - column.getType().getBinding() - )); + final List converter = out.ref(list(column.getType().getConverter())); + final List binding = out.ref(list(column.getType().getBinding())); out.tab(1).javadoc("The column %s.%s", column.getQualifiedOutputName(), defaultIfBlank(" " + escapeEntities(columnComment), "")); if (scala) { - out.tab(1).println("val %s : %s[%s, %s] = createField(\"%s\", %s, \"%s\"[[before=, ][new %s()]])", - columnId, TableField.class, recordType, columnType, columnName, columnTypeRef, escapeString(columnComment), converters); + out.tab(1).println("val %s : %s[%s, %s] = createField(\"%s\", %s, \"%s\"" + converterTemplate(converter) + converterTemplate(binding) + ")", + columnId, TableField.class, recordType, columnType, columnName, columnTypeRef, escapeString(columnComment), converter, binding); } else { String isStatic = generateInstanceFields() ? "" : "static "; String tableRef = generateInstanceFields() ? "this" : out.ref(getStrategy().getJavaIdentifier(table), 2); - out.tab(1).println("public %sfinal %s<%s, %s> %s = createField(\"%s\", %s, %s, \"%s\"[[before=, ][new %s()]]);", - isStatic, TableField.class, recordType, columnType, columnId, columnName, columnTypeRef, tableRef, escapeString(columnComment), converters); + out.tab(1).println("public %sfinal %s<%s, %s> %s = createField(\"%s\", %s, %s, \"%s\"" + converterTemplate(converter) + converterTemplate(binding) + ");", + isStatic, TableField.class, recordType, columnType, columnId, columnName, columnTypeRef, tableRef, escapeString(columnComment), converter, binding); } } @@ -3687,6 +3681,21 @@ public class JavaGenerator extends AbstractGenerator { closeJavaWriter(out); } + private String converterTemplate(List converter) { + if (converter == null || converter.isEmpty()) + return "[[]]"; + if (converter.size() > 1) + throw new IllegalArgumentException(); + switch (GenerationUtil.expressionType(converter.get(0))) { + case CONSTRUCTOR_REFERENCE: + return "[[before=, ][new %s()]]"; + case EXPRESSION: + return "[[before=, ][%s]]"; + default: + throw new IllegalArgumentException(); + } + } + private String escapeString(String comment) { if (comment == null) @@ -4172,17 +4181,18 @@ public class JavaGenerator extends AbstractGenerator { final String returnType = (routine.getReturnValue() == null) ? Void.class.getName() : out.ref(getJavaType(routine.getReturnType())); - final List returnTypeRef = list((routine.getReturnValue() != null) + final List returnTypeRef = list((routine.getReturnValue() != null) ? getJavaTypeReference(database, routine.getReturnType()) : null); - final List returnConverterType = out.ref(list( + final List returnConverter = out.ref(list( (routine.getReturnValue() != null) ? routine.getReturnType().getConverter() - : null, + : null)); + final List returnBinding = out.ref(list( (routine.getReturnValue() != null) ? routine.getReturnType().getBinding() - : null - )); + : null)); + final List interfaces = out.ref(getStrategy().getJavaClassImplements(routine, Mode.DEFAULT)); final String schemaId = out.ref(getStrategy().getFullJavaIdentifier(schema), 2); final List packageId = out.ref(getStrategy().getFullJavaIdentifiers(routine.getPackage()), 2); @@ -4218,8 +4228,8 @@ public class JavaGenerator extends AbstractGenerator { printClassAnnotations(out, schema); if (scala) { - out.println("class %s extends %s[%s](\"%s\", %s[[before=, ][%s]][[before=, ][%s]][[before=, ][new %s()]])[[before= with ][separator= with ][%s]] {", - className, AbstractRoutine.class, returnType, routine.getName(), schemaId, packageId, returnTypeRef, returnConverterType, interfaces); + out.println("class %s extends %s[%s](\"%s\", %s[[before=, ][%s]][[before=, ][%s]]" + converterTemplate(returnConverter) + converterTemplate(returnBinding) + ")[[before= with ][separator= with ][%s]] {", + className, AbstractRoutine.class, returnType, routine.getName(), schemaId, packageId, returnTypeRef, returnConverter, returnBinding, interfaces); } else { out.println("public class %s extends %s<%s>[[before= implements ][%s]] {", @@ -4234,15 +4244,13 @@ public class JavaGenerator extends AbstractGenerator { final String paramComment = StringUtils.defaultString(parameter.getComment()); final String isDefaulted = parameter.isDefaulted() ? "true" : "false"; final String isUnnamed = parameter.isUnnamed() ? "true" : "false"; - final List converters = out.ref(list( - parameter.getType().getConverter(), - parameter.getType().getBinding() - )); + final List converter = out.ref(list(parameter.getType().getConverter())); + final List binding = out.ref(list(parameter.getType().getBinding())); out.tab(1).javadoc("The parameter %s.%s", parameter.getQualifiedOutputName(), defaultIfBlank(" " + paramComment, "")); - out.tab(1).println("public static final %s<%s> %s = createParameter(\"%s\", %s, %s, %s[[before=, ][new %s()]]);", - Parameter.class, paramType, paramId, paramName, paramTypeRef, isDefaulted, isUnnamed, converters); + out.tab(1).println("public static final %s<%s> %s = createParameter(\"%s\", %s, %s, %s" + converterTemplate(returnConverter) + converterTemplate(returnBinding) + ");", + Parameter.class, paramType, paramId, paramName, paramTypeRef, isDefaulted, isUnnamed, converter, binding); } } @@ -4253,7 +4261,7 @@ public class JavaGenerator extends AbstractGenerator { else { out.tab(1).javadoc("Create a new routine call instance"); out.tab(1).println("public %s() {", className); - out.tab(2).println("super(\"%s\", %s[[before=, ][%s]][[before=, ][%s]][[before=, ][new %s()]]);", routine.getName(), schemaId, packageId, returnTypeRef, returnConverterType); + out.tab(2).println("super(\"%s\", %s[[before=, ][%s]][[before=, ][%s]]" + converterTemplate(returnConverter) + converterTemplate(returnBinding) + ");", routine.getName(), schemaId, packageId, returnTypeRef, returnConverter, returnBinding); if (routine.getAllParameters().size() > 0) { diff --git a/jOOQ-codegen/src/main/java/org/jooq/util/JavaWriter.java b/jOOQ-codegen/src/main/java/org/jooq/util/JavaWriter.java index 347688986c..3279d00455 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/util/JavaWriter.java +++ b/jOOQ-codegen/src/main/java/org/jooq/util/JavaWriter.java @@ -1,5 +1,8 @@ package org.jooq.util; +import static org.jooq.util.GenerationUtil.PLAIN_GENERIC_TYPE_PATTERN; +import static org.jooq.util.GenerationUtil.TYPE_REFERENCE_PATTERN; + import java.io.File; import java.io.PrintWriter; import java.util.ArrayList; @@ -33,8 +36,6 @@ public class JavaWriter extends GeneratorWriter { private final String className; private final boolean isJava; private final boolean isScala; - private final Pattern REF_PATTERN = Pattern.compile("((?:[\\p{L}_$][\\p{L}\\p{N}_$]*\\.)*[\\p{L}_$][\\p{L}\\p{N}_$]*)((?:<.*>|\\[.*\\])*)"); - private final Pattern PLAIN_GENERIC_TYPE_PATTERN = Pattern.compile("[<\\[]((?:[\\p{L}_$][\\p{L}\\p{N}_$]*\\.)*[\\p{L}_$][\\p{L}\\p{N}_$]*)[>\\]]"); public JavaWriter(File file, String fullyQualifiedTypes) { this(file, fullyQualifiedTypes, null); @@ -202,7 +203,7 @@ public class JavaWriter extends GeneratorWriter { // com.example.Table.TABLE.COLUMN (with keepSegments = 3) if (fullyQualifiedTypes == null || !fullyQualifiedTypes.matcher(c).matches()) { - Matcher m = REF_PATTERN.matcher(c); + Matcher m = TYPE_REFERENCE_PATTERN.matcher(c); if (m.find()) { diff --git a/jOOQ-meta/src/main/java/org/jooq/util/AbstractDatabase.java b/jOOQ-meta/src/main/java/org/jooq/util/AbstractDatabase.java index 5bc05a3510..daa9f6785e 100644 --- a/jOOQ-meta/src/main/java/org/jooq/util/AbstractDatabase.java +++ b/jOOQ-meta/src/main/java/org/jooq/util/AbstractDatabase.java @@ -905,8 +905,10 @@ public abstract class AbstractDatabase implements Database { continue; } - if (StringUtils.isBlank(type.getBinding()) && StringUtils.isBlank(type.getConverter())) { - log.warn("Bad configuration for . Either or is required: " + toString(type)); + if (StringUtils.isBlank(type.getBinding()) && + StringUtils.isBlank(type.getConverter()) && + !Boolean.TRUE.equals(type.isEnumConverter())) { + log.warn("Bad configuration for . Either or or is required: " + toString(type)); it2.remove(); continue; @@ -925,6 +927,10 @@ public abstract class AbstractDatabase implements Database { log.warn("Bad configuration for . is not allowed when is provided: " + toString(type)); type.setConverter(null); } + if (Boolean.TRUE.equals(type.isEnumConverter())) { + log.warn("Bad configuration for . is not allowed when is provided: " + toString(type)); + type.setEnumConverter(null); + } } if (type.getUserType() != null && StringUtils.equals(type.getUserType(), typeName)) { diff --git a/jOOQ-meta/src/main/java/org/jooq/util/AbstractTypedElementDefinition.java b/jOOQ-meta/src/main/java/org/jooq/util/AbstractTypedElementDefinition.java index 61e603f6e2..eed0d733f4 100644 --- a/jOOQ-meta/src/main/java/org/jooq/util/AbstractTypedElementDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/util/AbstractTypedElementDefinition.java @@ -52,6 +52,7 @@ import org.jooq.Name; import org.jooq.exception.SQLDialectNotSupportedException; import org.jooq.impl.DateAsTimestampBinding; import org.jooq.impl.DefaultDataType; +import org.jooq.impl.EnumConverter; import org.jooq.impl.SQLDataType; import org.jooq.tools.JooqLogger; import org.jooq.tools.StringUtils; @@ -137,27 +138,36 @@ abstract class AbstractTypedElementDefinition // [#677] Forced types for matching regular expressions ForcedType forcedType = db.getConfiguredForcedType(child, definedType); if (forcedType != null) { - String type = forcedType.getName(); + String uType = forcedType.getName(); String converter = null; String binding = result.getBinding(); CustomType customType = customType(db, forcedType); if (customType != null) { - type = (!StringUtils.isBlank(customType.getType())) + uType = (!StringUtils.isBlank(customType.getType())) ? customType.getType() : customType.getName(); - if (!StringUtils.isBlank(customType.getConverter())) + if (Boolean.TRUE.equals(customType.isEnumConverter())) { + String tType = DefaultDataType + .getDataType(db.getDialect(), definedType.getType(), definedType.getPrecision(), definedType.getScale()) + .getType() + .getName(); + + converter = "new " + EnumConverter.class.getName() + "<" + tType + ", " + uType + ">(" + tType + ".class, " + uType + ".class)"; + } + else if (!StringUtils.isBlank(customType.getConverter())) { converter = customType.getConverter(); + } if (!StringUtils.isBlank(customType.getBinding())) binding = customType.getBinding(); } - if (type != null) { + if (uType != null) { log.info("Forcing type", child + " with type " + definedType.getType() - + " into " + type + + " into " + uType + (converter != null ? " using converter " + converter : "") + (binding != null ? " using binding " + binding : "")); @@ -171,7 +181,7 @@ abstract class AbstractTypedElementDefinition int s = 0; // [#2486] Allow users to override length, precision, and scale - Matcher matcher = LENGTH_PRECISION_SCALE_PATTERN.matcher(type); + Matcher matcher = LENGTH_PRECISION_SCALE_PATTERN.matcher(uType); if (matcher.find()) { if (!isEmpty(matcher.group(1))) { l = p = convert(matcher.group(1), int.class); @@ -183,12 +193,12 @@ abstract class AbstractTypedElementDefinition } try { - forcedDataType = DefaultDataType.getDataType(db.getDialect(), type, p, s); + forcedDataType = DefaultDataType.getDataType(db.getDialect(), uType, p, s); } catch (SQLDialectNotSupportedException ignore) {} // [#677] SQLDataType matches are actual type-rewrites if (forcedDataType != null) { - result = new DefaultDataTypeDefinition(db, child.getSchema(), type, l, p, s, n, d, (Name) null, converter, binding); + result = new DefaultDataTypeDefinition(db, child.getSchema(), uType, l, p, s, n, d, (Name) null, converter, binding); } // Other forced types are UDT's, enums, etc. @@ -198,7 +208,7 @@ abstract class AbstractTypedElementDefinition s = result.getScale(); String t = result.getType(); Name u = result.getQualifiedUserType(); - result = new DefaultDataTypeDefinition(db, child.getSchema(), t, l, p, s, n, d, u, converter, binding, type); + result = new DefaultDataTypeDefinition(db, child.getSchema(), t, l, p, s, n, d, u, converter, binding, uType); } // [#4597] If we don't have a type-rewrite (forcedDataType) or a @@ -235,6 +245,7 @@ abstract class AbstractTypedElementDefinition else { return new CustomType() .withBinding(forcedType.getBinding()) + .withEnumConverter(forcedType.isEnumConverter()) .withConverter(forcedType.getConverter()) .withName(name) .withType(forcedType.getUserType()); diff --git a/jOOQ-meta/src/main/resources/xsd/jooq-codegen-3.10.0.xsd b/jOOQ-meta/src/main/resources/xsd/jooq-codegen-3.10.0.xsd index 02dbad70b4..3a3100a489 100644 --- a/jOOQ-meta/src/main/resources/xsd/jooq-codegen-3.10.0.xsd +++ b/jOOQ-meta/src/main/resources/xsd/jooq-codegen-3.10.0.xsd @@ -717,8 +717,13 @@ the "type" value will default to the "name" value. --> - - + + + + + + + @@ -746,8 +751,13 @@ or is required --> - - + + + + + + +