Skip to content

Commit

Permalink
[#5877] [#5884] Improved code generator configuration
Browse files Browse the repository at this point in the history
- [#5877] Add <enumConverter/> flag in <forcedType/> to auto-generate EnumConverter
- [#5884] Allow for specifying Java expressions as Converter / Binding configurations
  • Loading branch information
lukaseder committed Feb 17, 2017
1 parent 5f72c60 commit c52864c
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 50 deletions.
18 changes: 18 additions & 0 deletions jOOQ-codegen/src/main/java/org/jooq/util/GenerationUtil.java
Expand Up @@ -39,9 +39,12 @@
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;
Expand All @@ -55,6 +58,9 @@
*/
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",
Expand Down Expand Up @@ -414,4 +420,16 @@ public static Integer[] range(Integer from, Integer to) {

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
}
}
72 changes: 40 additions & 32 deletions jOOQ-codegen/src/main/java/org/jooq/util/JavaGenerator.java
Expand Up @@ -1660,19 +1660,17 @@ protected void generateUDT(UDTDefinition udt, JavaWriter out) {
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);
}
}

Expand Down Expand Up @@ -1995,8 +1993,6 @@ protected void generateArray(ArrayDefinition array, JavaWriter out) {








Expand Down Expand Up @@ -3325,23 +3321,21 @@ protected void generateTable(TableDefinition table, JavaWriter out) {
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);
}
}

Expand Down Expand Up @@ -3687,6 +3681,21 @@ else if (generateInstanceFields()) {
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)
Expand Down Expand Up @@ -4172,17 +4181,18 @@ protected void generateRoutine(RoutineDefinition routine, JavaWriter out) {
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);
Expand Down Expand Up @@ -4218,8 +4228,8 @@ protected void generateRoutine(RoutineDefinition routine, JavaWriter out) {
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]] {",
Expand All @@ -4234,15 +4244,13 @@ protected void generateRoutine(RoutineDefinition routine, JavaWriter out) {
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);
}
}

Expand All @@ -4253,7 +4261,7 @@ protected void generateRoutine(RoutineDefinition routine, JavaWriter out) {
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) {
Expand Down
7 changes: 4 additions & 3 deletions 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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -202,7 +203,7 @@ protected List<String> ref(List<String> clazz, int keepSegments) {

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

Expand Down
10 changes: 8 additions & 2 deletions jOOQ-meta/src/main/java/org/jooq/util/AbstractDatabase.java
Expand Up @@ -905,8 +905,10 @@ public final CustomType getConfiguredCustomType(String typeName) {
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;
Expand All @@ -925,6 +927,10 @@ public final CustomType getConfiguredCustomType(String typeName) {
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)) {
Expand Down
Expand Up @@ -52,6 +52,7 @@
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;
Expand Down Expand Up @@ -137,27 +138,36 @@ static DataTypeDefinition mapDefinedType(Definition container, Definition child,
// [#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 : ""));

Expand All @@ -171,7 +181,7 @@ static DataTypeDefinition mapDefinedType(Definition container, Definition child,
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);
Expand All @@ -183,12 +193,12 @@ static DataTypeDefinition mapDefinedType(Definition container, Definition child,
}

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.
Expand All @@ -198,7 +208,7 @@ else if (customType != null) {
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
Expand Down Expand Up @@ -235,6 +245,7 @@ static CustomType customType(Database db, ForcedType forcedType) {
else {
return new CustomType()
.withBinding(forcedType.getBinding())
.withEnumConverter(forcedType.isEnumConverter())
.withConverter(forcedType.getConverter())
.withName(name)
.withType(forcedType.getUserType());
Expand Down

0 comments on commit c52864c

Please sign in to comment.