[#5877] [#5884] Improved code generator configuration

- [#5877] Add <enumConverter/> flag in <forcedType/> to auto-generate EnumConverter
- [#5884] Allow for specifying Java expressions as Converter / Binding configurations
This commit is contained in:
lukaseder 2017-02-17 14:32:57 +01:00
parent 5f72c6080f
commit c52864cbf5
6 changed files with 104 additions and 50 deletions

View File

@ -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<String> JAVA_KEYWORDS = unmodifiableSet(new HashSet<String>(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
}
}

View File

@ -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<String> converters = out.ref(list(
attribute.getType().getConverter(),
attribute.getType().getBinding()
));
final List<String> converter = out.ref(list(attribute.getType().getConverter()));
final List<String> 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 <code>%s</code>.%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<String> converters = out.ref(list(
column.getType().getConverter(),
column.getType().getBinding()
));
final List<String> converter = out.ref(list(column.getType().getConverter()));
final List<String> binding = out.ref(list(column.getType().getBinding()));
out.tab(1).javadoc("The column <code>%s</code>.%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<String> 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<String> returnTypeRef = list((routine.getReturnValue() != null)
? getJavaTypeReference(database, routine.getReturnType())
: null);
final List<?> returnConverterType = out.ref(list(
final List<String> returnConverter = out.ref(list(
(routine.getReturnValue() != null)
? routine.getReturnType().getConverter()
: null,
: null));
final List<String> returnBinding = out.ref(list(
(routine.getReturnValue() != null)
? routine.getReturnType().getBinding()
: null
));
: null));
final List<String> interfaces = out.ref(getStrategy().getJavaClassImplements(routine, Mode.DEFAULT));
final String schemaId = out.ref(getStrategy().getFullJavaIdentifier(schema), 2);
final List<String> 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<String> converters = out.ref(list(
parameter.getType().getConverter(),
parameter.getType().getBinding()
));
final List<String> converter = out.ref(list(parameter.getType().getConverter()));
final List<String> binding = out.ref(list(parameter.getType().getBinding()));
out.tab(1).javadoc("The parameter <code>%s</code>.%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) {

View File

@ -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<JavaWriter> {
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<JavaWriter> {
// 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()) {

View File

@ -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 <forcedType/>. Either <binding/> or <converter/> is required: " + toString(type));
if (StringUtils.isBlank(type.getBinding()) &&
StringUtils.isBlank(type.getConverter()) &&
!Boolean.TRUE.equals(type.isEnumConverter())) {
log.warn("Bad configuration for <forcedType/>. Either <binding/> or <converter/> or <enumConverter/> is required: " + toString(type));
it2.remove();
continue;
@ -925,6 +927,10 @@ public abstract class AbstractDatabase implements Database {
log.warn("Bad configuration for <forcedType/>. <converter/> is not allowed when <name/> is provided: " + toString(type));
type.setConverter(null);
}
if (Boolean.TRUE.equals(type.isEnumConverter())) {
log.warn("Bad configuration for <forcedType/>. <enumConverter/> is not allowed when <name/> is provided: " + toString(type));
type.setEnumConverter(null);
}
}
if (type.getUserType() != null && StringUtils.equals(type.getUserType(), typeName)) {

View File

@ -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<T extends Definition>
// [#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<T extends Definition>
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<T extends Definition>
}
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<T extends Definition>
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<T extends Definition>
else {
return new CustomType()
.withBinding(forcedType.getBinding())
.withEnumConverter(forcedType.isEnumConverter())
.withConverter(forcedType.getConverter())
.withName(name)
.withType(forcedType.getUserType());

View File

@ -717,8 +717,13 @@
the "type" value will default to the "name" value. -->
<element name="type" type="string" minOccurs="0" maxOccurs="1" />
<!-- A converter implementation for the custom type -->
<element name="converter" type="string" minOccurs="0" maxOccurs="1" />
<choice>
<!-- A converter implementation for the custom type -->
<element name="converter" type="string" minOccurs="0" maxOccurs="1" />
<!-- A converter implementation for the custom type -->
<element name="enumConverter" type="boolean" minOccurs="0" maxOccurs="1" fixed="true" />
</choice>
<!-- A binding implementation for the custom type -->
<element name="binding" type="string" minOccurs="0" maxOccurs="1" />
@ -746,8 +751,13 @@
or <binding/> is required -->
<element name="userType" type="string" minOccurs="0" maxOccurs="1" />
<!-- A converter implementation for the user type. -->
<element name="converter" type="string" minOccurs="0" maxOccurs="1" />
<choice>
<!-- A converter implementation for the custom type -->
<element name="converter" type="string" minOccurs="0" maxOccurs="1" />
<!-- A converter implementation for the custom type -->
<element name="enumConverter" type="boolean" minOccurs="0" maxOccurs="1" />
</choice>
<!-- A binding implementation for the custom type. -->
<element name="binding" type="string" minOccurs="0" maxOccurs="1" />