24
24
import jakarta .persistence .TypedQuery ;
25
25
26
26
import java .lang .reflect .Constructor ;
27
+ import java .util .ArrayList ;
27
28
import java .util .Arrays ;
28
29
import java .util .Collection ;
29
30
import java .util .HashMap ;
34
35
import java .util .stream .Collectors ;
35
36
36
37
import org .springframework .beans .BeanUtils ;
38
+ import org .springframework .core .MethodParameter ;
37
39
import org .springframework .core .convert .converter .Converter ;
38
40
import org .springframework .data .jpa .provider .PersistenceProvider ;
39
41
import org .springframework .data .jpa .repository .EntityGraph ;
56
58
import org .springframework .lang .Nullable ;
57
59
import org .springframework .util .Assert ;
58
60
import org .springframework .util .ClassUtils ;
59
- import org .springframework .util .ReflectionUtils ;
60
61
61
62
/**
62
63
* Abstract base class to implement {@link RepositoryQuery}s.
@@ -347,7 +348,7 @@ public TupleConverter(ReturnedType type, boolean nativeQuery) {
347
348
&& !type .getInputProperties ().isEmpty ();
348
349
349
350
if (this .dtoProjection ) {
350
- this .preferredConstructor = PreferredConstructorDiscoverer .discover (type .getReturnedType ());
351
+ this .preferredConstructor = PreferredConstructorDiscoverer .discover (type .getReturnedType ());
351
352
} else {
352
353
this .preferredConstructor = null ;
353
354
}
@@ -373,67 +374,97 @@ public Object convert(Object source) {
373
374
374
375
if (dtoProjection ) {
375
376
376
- Object [] ctorArgs = new Object [type .getInputProperties ().size ()];
377
+ Object [] ctorArgs = new Object [elements .size ()];
378
+ for (int i = 0 ; i < ctorArgs .length ; i ++) {
379
+ ctorArgs [i ] = tuple .get (i );
380
+ }
381
+
382
+ List <Class <?>> argTypes = getArgumentTypes (ctorArgs );
377
383
378
- boolean containsNullValue = false ;
379
- for (int i = 0 ; i < type .getInputProperties ().size (); i ++) {
380
- Object value = tuple .get (i );
381
- ctorArgs [i ] = value ;
382
- if (!containsNullValue && value == null ) {
383
- containsNullValue = true ;
384
- }
384
+ if (preferredConstructor != null && isConstructorCompatible (preferredConstructor .getConstructor (), argTypes )) {
385
+ return BeanUtils .instantiateClass (preferredConstructor .getConstructor (), ctorArgs );
385
386
}
386
387
387
- try {
388
+ return BeanUtils .instantiateClass (getFirstMatchingConstructor (ctorArgs , argTypes ), ctorArgs );
389
+ }
388
390
389
- if (preferredConstructor != null && preferredConstructor .getParameterCount () == ctorArgs .length ) {
390
- return BeanUtils .instantiateClass (preferredConstructor .getConstructor (), ctorArgs );
391
- }
391
+ return new TupleBackedMap (tupleWrapper .apply (tuple ));
392
+ }
392
393
393
- Constructor <?> ctor = null ;
394
+ private Constructor <?> getFirstMatchingConstructor ( Object [] ctorArgs , List < Class <?>> argTypes ) {
394
395
395
- if (!containsNullValue ) { // let's seem if we have an argument type match
396
- ctor = type .getReturnedType ()
397
- .getConstructor (Arrays .stream (ctorArgs ).map (Object ::getClass ).toArray (Class <?>[]::new ));
398
- }
396
+ for (Constructor <?> ctor : type .getReturnedType ().getDeclaredConstructors ()) {
399
397
400
- if (ctor == null ) { // let's see if there's more general constructor we could use that accepts our args
401
- ctor = findFirstMatchingConstructor ( ctorArgs ) ;
402
- }
398
+ if (ctor . getParameterCount () != ctorArgs . length ) {
399
+ continue ;
400
+ }
403
401
404
- if (ctor != null ) {
405
- return BeanUtils .instantiateClass (ctor , ctorArgs );
406
- }
407
- } catch (ReflectiveOperationException e ) {
408
- ReflectionUtils .handleReflectionException (e );
402
+ if (isConstructorCompatible (ctor , argTypes )) {
403
+ return ctor ;
409
404
}
410
405
}
411
406
412
- return new TupleBackedMap (tupleWrapper .apply (tuple ));
407
+ throw new IllegalStateException (String .format (
408
+ "Cannot find compatible constructor for DTO projection '%s' accepting '%s'" , type .getReturnedType ().getName (),
409
+ argTypes .stream ().map (Class ::getName ).collect (Collectors .joining (", " ))));
413
410
}
414
411
415
- @ Nullable
416
- private Constructor <?> findFirstMatchingConstructor ( Object [] ctorArgs ) {
412
+ private static List < Class <?>> getArgumentTypes ( Object [] ctorArgs ) {
413
+ List < Class <?>> argTypes = new ArrayList <>( ctorArgs . length );
417
414
418
- for (Constructor <?> ctor : type .getReturnedType ().getDeclaredConstructors ()) {
415
+ for (Object ctorArg : ctorArgs ) {
416
+ argTypes .add (ctorArg == null ? Void .class : ctorArg .getClass ());
417
+ }
418
+ return argTypes ;
419
+ }
419
420
420
- if (ctor .getParameterCount () == ctorArgs .length ) {
421
- boolean itsAMatch = true ;
422
- for (int i = 0 ; i < ctor .getParameterCount (); i ++) {
423
- if (ctorArgs [i ] == null ) {
424
- continue ;
425
- }
426
- if (!ClassUtils .isAssignable (ctor .getParameterTypes ()[i ], ctorArgs [i ].getClass ())) {
427
- itsAMatch = false ;
428
- break ;
429
- }
430
- }
431
- if (itsAMatch ) {
432
- return ctor ;
433
- }
421
+ public static boolean isConstructorCompatible (Constructor <?> constructor , List <Class <?>> argumentTypes ) {
422
+
423
+ if (constructor .getParameterCount () != argumentTypes .size ()) {
424
+ return false ;
425
+ }
426
+
427
+ for (int i = 0 ; i < argumentTypes .size (); i ++) {
428
+
429
+ MethodParameter methodParameter = MethodParameter .forExecutable (constructor , i );
430
+ Class <?> argumentType = argumentTypes .get (i );
431
+
432
+ if (!areAssignmentCompatible (methodParameter .getParameterType (), argumentType )) {
433
+ return false ;
434
434
}
435
435
}
436
- return null ;
436
+ return true ;
437
+ }
438
+
439
+ private static boolean areAssignmentCompatible (Class <?> to , Class <?> from ) {
440
+
441
+ if (from == Void .class && !to .isPrimitive ()) {
442
+ // treat Void as the bottom type, the class of null
443
+ return true ;
444
+ }
445
+
446
+ if (to .isPrimitive ()) {
447
+
448
+ if (to == Short .TYPE ) {
449
+ return from == Character .class || from == Byte .class ;
450
+ }
451
+
452
+ if (to == Integer .TYPE ) {
453
+ return from == Short .class || from == Character .class || from == Byte .class ;
454
+ }
455
+
456
+ if (to == Long .TYPE ) {
457
+ return from == Integer .class || from == Short .class || from == Character .class || from == Byte .class ;
458
+ }
459
+
460
+ if (to == Double .TYPE ) {
461
+ return from == Float .class ;
462
+ }
463
+
464
+ return ClassUtils .isAssignable (to , from );
465
+ }
466
+
467
+ return ClassUtils .isAssignable (to , from );
437
468
}
438
469
439
470
/**
0 commit comments