[jOOQ/jOOQ#18568] NullPointerException when working with Scala-3

generated enums (or Java enum literals with bodies)
This commit is contained in:
Lukas Eder 2025-06-10 11:06:37 +02:00
parent 0a94756c42
commit ded3838351
5 changed files with 50 additions and 43 deletions

View File

@ -40,6 +40,7 @@ package org.jooq;
import static java.util.Arrays.stream;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;
import static org.jooq.impl.Internal.enums;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -55,7 +56,7 @@ final class EnumTypes {
// Avoid intersection type because of Eclipse compiler bug:
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=577466
static <E extends /* Enum<E> & */ EnumType> E lookupLiteral(Class<E> enumType, String literal) {
return (E) LOOKUP.computeIfAbsent(enumType, t -> stream(enumType.getEnumConstants()).collect(toMap(E::getLiteral, identity()))).get(literal);
return (E) LOOKUP.computeIfAbsent(enumType, t -> stream(enums(enumType)).collect(toMap(E::getLiteral, identity()))).get(literal);
}
private EnumTypes() {}

View File

@ -46,6 +46,7 @@ import static org.jooq.impl.Internal.arrayType;
import static org.jooq.impl.Internal.converterContext;
import static org.jooq.impl.Tools.configuration;
import static org.jooq.impl.Tools.emulateMultiset;
import static org.jooq.impl.Tools.enums;
import static org.jooq.tools.StringUtils.leftPad;
import static org.jooq.tools.reflect.Reflect.accessible;
import static org.jooq.tools.reflect.Reflect.wrapper;
@ -1321,16 +1322,10 @@ final class Convert {
if (fromString == null)
return null;
if (EnumType.class.isAssignableFrom(toClass)) {
for (Object value : toClass.getEnumConstants())
if (fromString.equals(((EnumType) value).getLiteral()))
return (U) value;
return null;
}
else {
if (EnumType.class.isAssignableFrom(toClass))
return (U) EnumType.lookupLiteral((Class) toClass, fromString);
else
return (U) java.lang.Enum.valueOf((Class) toClass, fromString);
}
}
catch (IllegalArgumentException e) {

View File

@ -76,7 +76,7 @@ public /* non-final */ class EnumConverter<T, U extends Enum<U>> extends Abstrac
this.to = to;
this.lookup = new LinkedHashMap<>();
for (U u : toType.getEnumConstants()) {
for (U u : Internal.enums(toType)) {
T key = to(u);
if (key != null)

View File

@ -69,6 +69,7 @@ import org.jooq.DDLExportConfiguration;
import org.jooq.DataType;
import org.jooq.Domain;
import org.jooq.EmbeddableRecord;
import org.jooq.EnumType;
import org.jooq.Field;
import org.jooq.ForeignKey;
import org.jooq.Generator;
@ -110,6 +111,7 @@ import org.jooq.UniqueKey;
// ...
import org.jooq.exception.DataAccessException;
import org.jooq.exception.DataTypeException;
import org.jooq.exception.MappingException;
import org.jooq.impl.QOM.CreateTable;
import org.jooq.impl.QOM.ForeignKeyRule;
import org.jooq.impl.QOM.GenerationLocation;
@ -1287,4 +1289,39 @@ public final class Internal {
return result;
}
/**
* A utility to list enum literals from a {@link Class}, independently of
* language implementation.
*/
@SuppressWarnings("unchecked")
public static final <E> E[] enums(Class<? extends E> type) {
// Java implementation
if (Enum.class.isAssignableFrom(type)) {
E[] result = type.getEnumConstants();
// [#18568] Enums may be subclasses of the declaring enum type, e.g. enum E { a {} } or Scala 3 enums.
if (result == null && type.getSuperclass() != Enum.class)
result = (E[]) type.getSuperclass().getEnumConstants();
return result;
}
// [#4427] Scala implementation
else {
try {
// There's probably a better way to do this:
// http://stackoverflow.com/q/36068089/521799
Class<?> companionClass = Thread.currentThread().getContextClassLoader().loadClass(type.getName() + "$");
java.lang.reflect.Field module = companionClass.getField("MODULE$");
Object companion = module.get(companionClass);
return (E[]) companionClass.getMethod("values").invoke(companion);
}
catch (Exception e) {
throw new MappingException("Error while looking up Scala enum", e);
}
}
}
}

View File

@ -6106,7 +6106,7 @@ final class Tools {
ctx.visit(K_ENUM).sql('(');
String separator = "";
for (EnumType e : enumConstants(enumType)) {
for (EnumType e : enums(enumType)) {
ctx.sql(separator).visit(DSL.inline(e.getLiteral()));
separator = ", ";
}
@ -6244,20 +6244,15 @@ final class Tools {
}
static final boolean storedEnumType(DataType<EnumType> enumType) {
return enumConstants(enumType)[0].getName() != null;
return enums(enumType)[0].getName() != null;
}
private static final EnumType[] enumConstants(DataType<? extends EnumType> type) {
EnumType[] enums = type.getType().getEnumConstants();
if (enums == null)
throw new DataTypeException("EnumType must be a Java enum");
return enums;
static final EnumType[] enums(DataType<? extends EnumType> type) {
return enums(type.getType());
}
static final DataType<String> emulateEnumType(DataType<? extends EnumType> type) {
return emulateEnumType(type, enumConstants(type));
return emulateEnumType(type, enums(type));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@ -6339,29 +6334,8 @@ final class Tools {
};
}
@SuppressWarnings("unchecked")
static final <E extends EnumType> E[] enums(Class<? extends E> type) {
// Java implementation
if (Enum.class.isAssignableFrom(type)) {
return type.getEnumConstants();
}
// [#4427] Scala implementation
else {
try {
// There's probably a better way to do this:
// http://stackoverflow.com/q/36068089/521799
Class<?> companionClass = Thread.currentThread().getContextClassLoader().loadClass(type.getName() + "$");
java.lang.reflect.Field module = companionClass.getField("MODULE$");
Object companion = module.get(companionClass);
return (E[]) companionClass.getMethod("values").invoke(companion);
}
catch (Exception e) {
throw new MappingException("Error while looking up Scala enum", e);
}
}
return Internal.enums(type);
}
/**