77import org .jspecify .annotations .Nullable ;
88import org .springframework .data .domain .Sort ;
99
10+ import java .lang .reflect .Method ;
11+ import java .lang .reflect .Modifier ;
1012import java .util .ArrayList ;
1113import java .util .List ;
1214import java .util .Map ;
2426@ NullMarked
2527public final class SortParameter <T extends Enum <?> & SortParameter .Definition > {
2628 private static final String DEFAULT_SORT_PROPERTY = "id" ;
27- private static final Sort .Direction DEFAULT_SORT_DIRETION = Sort .Direction .ASC ;
29+ private static final Sort .Direction DEFAULT_SORT_DIRECTION = Sort .Direction .ASC ;
2830
2931 @ Accessors (fluent = true )
3032 @ Getter
@@ -226,11 +228,11 @@ public SortParameter<T> or(SortParameter<T> fallback) {
226228 * @return an instance of {@link Sort} created using the transformed key-value mapping,
227229 * excluding default sorting behavior.
228230 */
229- public Sort buildSortWithoutDefault (Map <T , String > mapping ) {
231+ public Sort buildSortWithoutDefault (Map <T , ? > mapping ) {
230232 var stringMapping = mapping .entrySet ().stream ()
231233 .collect (Collectors .toMap (
232234 entry -> entry .getKey ().name (),
233- Map . Entry :: getValue
235+ entry -> convertToString ( entry . getValue ())
234236 ));
235237
236238 return buildSort (stringMapping , false );
@@ -246,16 +248,68 @@ public Sort buildSortWithoutDefault(Map<T, String> mapping) {
246248 * The enumeration keys must implement the `name()` method to retrieve their string representation.
247249 * @return an instance of {@link Sort} created using the transformed key-value mapping with default sorting behavior.
248250 */
249- public Sort buildSort (Map <T , String > mapping ) {
251+ public Sort buildSort (Map <T , ? > mapping ) {
250252 var stringMapping = mapping .entrySet ().stream ()
251253 .collect (Collectors .toMap (
252254 entry -> entry .getKey ().name (),
253- Map . Entry :: getValue
255+ entry -> convertToString ( entry . getValue ())
254256 ));
255257
256258 return buildSort (stringMapping , true );
257259 }
258260
261+ private static String convertToString (Object value ) {
262+ if (value instanceof String string ) {
263+ return string ;
264+ }
265+
266+ // Use reflection to get the column name from a mapped jOOQ Field<?> using the Field<?>.getName() method
267+ // This allows us to have jOOQ as an optional dependency and not break existing projects that use Hibernate
268+ try {
269+ var getName = findAccessibleGetNameMethod (value .getClass ());
270+ if (getName == null ) {
271+ getName = value .getClass ().getMethod ("getName" );
272+ getName .setAccessible (true );
273+ }
274+
275+ return (String ) getName .invoke (value );
276+ } catch (Exception e ) {
277+ throw new IllegalArgumentException (
278+ "Cannot get the jOOQ Field name from the mapped SortMappings$Mapping column value [value.class.name=%s]" .formatted (
279+ value .getClass ().getName ()
280+ ),
281+ e
282+ );
283+ }
284+ }
285+
286+ private static @ Nullable Method findAccessibleGetNameMethod (Class <?> clazz ) {
287+ if (Modifier .isPublic (clazz .getModifiers ())) {
288+ try {
289+ var method = clazz .getDeclaredMethod ("getName" );
290+ if (Modifier .isPublic (method .getModifiers ())) {
291+ return method ;
292+ }
293+ } catch (NoSuchMethodException _) {
294+ // ignored, let's search in the interfaces or superclass
295+ }
296+ }
297+
298+ for (var interFace : clazz .getInterfaces ()) {
299+ var method = findAccessibleGetNameMethod (interFace );
300+ if (method != null ) {
301+ return method ;
302+ }
303+ }
304+
305+ var superclass = clazz .getSuperclass ();
306+ if (superclass != null ) {
307+ return findAccessibleGetNameMethod (superclass );
308+ }
309+
310+ return null ;
311+ }
312+
259313 private Sort buildSort (Map <String , String > mapping , boolean withDefault ) {
260314 if (sortFields .isEmpty ()) {
261315 return withDefault ? getMappedDefaultSort (mapping ) : Sort .unsorted ();
@@ -289,7 +343,7 @@ private Sort buildSort(Map<String, String> mapping, boolean withDefault) {
289343 }
290344
291345 private static Sort getMappedDefaultSort (Map <String , String > mapping ) {
292- return Sort .by (DEFAULT_SORT_DIRETION , mapping .getOrDefault (DEFAULT_SORT_PROPERTY , DEFAULT_SORT_PROPERTY ));
346+ return Sort .by (DEFAULT_SORT_DIRECTION , mapping .getOrDefault (DEFAULT_SORT_PROPERTY , DEFAULT_SORT_PROPERTY ));
293347 }
294348
295349 public record SortField (
0 commit comments