Hello Christopher,
> If you want to influence the application design, then of course you
> can make up the rules.
That is not a goal, but a consequence from the current design, as you've
noticed.
> If you want to help users simplify their database access, then re-use
> their enums.
That would be very nice.
> If that means the generator should create a converter,
> where users can define the rules for the bridge, then why not.
Generating the converter... Why didn't we talk about that before! Finally,
the idea that convinces me! :-)
This will circumvent the need for naming conventions / marker interfaces /
annotations / other means enum recognition. Let me review your steps with
that in mind
> 1. Say we have this xx.SomeEnum declaration in a codebase:
Fine.
> 2. Then specify to the generator the logic for each enum:
> enum: xx.SomeEnum
> valueType: int
> valueGetter: intValue
> enumLookup: getEnum
> sqlDataType: SMALLINT
How about this instead:
enum: xx.SomeEnum
sqlDataType: SMALLINT
valueGetter: intValue (optional, defaults to Enum.name(), Enum.ordinal(),
depending on sqlDataType)
valueType is not needed as the type is sufficiently defined by SMALLINT.
>From both your code as well as from mine, below, I don't see a need for a
distinction between int/Integer, short/Short, etc
Also, the enumLookup is not needed, as the generated lookup class could
maintain its own generated reverse lookup.
> 3. I declare for the generator the column mapping of that enum:
> TableX.ColumnY: xx.SomeEnum
Yes. With regular expression support as usual.
> 4. The generator builds a central lookup class to convert the value
> to/from an object.
> This would be used by jOOQ when using/retrieving a value to/from the DB.
That's a good start. How about this alternative here (checked with an
Eclipse Java 6 compiler):
This is the same as you proposed:
----------------------------------------------------------------
interface EnumMapper<E extends Enum<E>, T> {
E toEnum(T x);
T fromEnum(E e);
DataType<T> getDataType(E e);
}
enum SomeEnum {
A(1),
B(2),
C(3);
private final int val;
private SomeEnum(int val) {
this.val = val;
}
public int intVal() {
return val;
}
}
----------------------------------------------------------------
The lookup is slightly different. If jOOQ can generate things, why not
generate a static final mapper instance for every configured enum? This
instance is globally accessible, just like any other generated artefact.
----------------------------------------------------------------
public class EnumLookup {
public static final EnumMapper<SomeEnum, Integer> SOME_ENUM_MAPPER = new
SomeEnumMapper();
private static final class SomeEnumMapper
implements EnumMapper<SomeEnum, Integer> {
// No need to provide jOOQ with a static method
// reference for the reverse lookup. jOOQ-generated
// mappers can calculate that for themselves.
// Unless I'm missing something?
final Map<Integer, SomeEnum> reverseLookup =
new HashMap<Integer, SomeEnum>();
public SomeEnumMapper() {
for (SomeEnum e : SomeEnum.values()) {
// intVal() is known to be the relevant method
reverseLookup.put(e.intVal(), e);
}
}
@Override
public SomeEnum toEnum(Integer x) {
return reverseLookup.get(x);
}
@Override
public Integer fromEnum(SomeEnum e) {
return e.intVal();
}
@Override
public DataType<Integer> getDataType(SomeEnum e) {
return SQLDataType.INTEGER;
}
}
}
----------------------------------------------------------------
Last but not least, the mapped field can reference the global static
mapper. I'll surely find a way to get the mapper through to the relevant
parts of jOOQ's type system.
----------------------------------------------------------------
final class TableX extends TableImpl<TableXRecord> {
public static final TableX TABLE_X = new TableX();
public final TableField<TableXRecord, SomeEnum> COLUMN_Y =
createField(
"COLUMN_Y",
SQLDataType.INTEGER.asEnumDataType(
SomeEnum.class,
EnumLookup.SOME_ENUM_MAPPER),
TABLE_X);
}
----------------------------------------------------------------
> This work for any type of literal, whether they are object or
> primitive type. Bonus: we have a central place where we see all enum
> declarations and how they get translated.
Exactly. It's a very nice approach! There's not even any reflection
involved!
> Other things could get added to the generator description like whether
> a null enumeration is allowed (when an object literal is used).
No need to explicitly specify this. If intVal() were integerVal(), your
SomeEnum class could return null for one enum value, which would be treated
just the same as all other values in SomeEnumMapper's constructor,
initialising the reverse lookup. toEnum(null) would then return X, when
fromEnum(X) returns null
> I am sure the mapper solves these kind of issues, by boxing/unboxing
> (basically adapting) primitive to objects.
Right.
> Please let me know what you think!
I think this is the best solution possible. The key to success is to
generate a mapper from only three pieces of information:
- SQL data type
- Enum class
- Optional enum value accessor method (defaulting to Enum.name() for
VARCHAR types, Enum.ordinal() for NUMBER types)
The existing, experimental functionality can be kept as is. For some users,
it's nice to have the "custom" enum class generated, too. The generated
enum classes (both synthetic and true ones, from MySQL, Postgres) can live
without that marker interface. The generated mapper replaces it.
What can I say? Thanks for insisting! :-)
I guess I can finally officially support this feature in the next minor
release.
Please also review my suggestions for potential flaws, misunderstandings. I
think I'll have time for a first draft implementation this Friday
Cheers
Lukas