83
83
import ai .timefold .solver .core .preview .api .domain .metamodel .PlanningSolutionMetaModel ;
84
84
import ai .timefold .solver .core .preview .api .domain .solution .diff .PlanningSolutionDiff ;
85
85
86
+ import org .jspecify .annotations .NonNull ;
86
87
import org .slf4j .Logger ;
87
88
import org .slf4j .LoggerFactory ;
88
89
@@ -395,20 +396,9 @@ Maybe add a getScore() method with a @%s annotation."""
395
396
}
396
397
397
398
private void processSolutionAnnotations (DescriptorPolicy descriptorPolicy ) {
398
- var solutionAnnotation = solutionClass .getAnnotation (PlanningSolution .class );
399
- var parentSolutionAnnotation =
400
- solutionClass .getSuperclass () != null ? solutionClass .getSuperclass ().getAnnotation (PlanningSolution .class )
401
- : null ;
402
- if (solutionAnnotation == null && parentSolutionAnnotation == null ) {
403
- throw new IllegalStateException (
404
- "The solutionClass (%s) has been specified as a solution in the configuration, but does not have a @%s annotation."
405
- .formatted (solutionClass , PlanningSolution .class .getSimpleName ()));
406
- }
407
- var annotation = solutionAnnotation != null ? solutionAnnotation : parentSolutionAnnotation ;
399
+ var annotation = extractMostRelevantPlanningSolutionAnnotation ();
408
400
autoDiscoverMemberType = annotation .autoDiscoverMemberType ();
409
- // We accept only the child class cloner
410
- var solutionClonerClass =
411
- solutionAnnotation != null ? solutionAnnotation .solutionCloner () : PlanningSolution .NullSolutionCloner .class ;
401
+ var solutionClonerClass = annotation .solutionCloner ();
412
402
if (solutionClonerClass != PlanningSolution .NullSolutionCloner .class ) {
413
403
solutionCloner = ConfigUtils .newInstance (this ::toString , "solutionClonerClass" , solutionClonerClass );
414
404
}
@@ -417,6 +407,29 @@ private void processSolutionAnnotations(DescriptorPolicy descriptorPolicy) {
417
407
new LookUpStrategyResolver (descriptorPolicy , lookUpStrategyType );
418
408
}
419
409
410
+ private @ NonNull PlanningSolution extractMostRelevantPlanningSolutionAnnotation () {
411
+ var solutionAnnotation = solutionClass .getAnnotation (PlanningSolution .class );
412
+ if (solutionAnnotation != null ) {
413
+ return solutionAnnotation ;
414
+ }
415
+ var solutionSuperclass = solutionClass .getSuperclass (); // Null if interface.
416
+ if (solutionSuperclass == null ) {
417
+ throw new IllegalStateException ("""
418
+ The solutionClass (%s) has been specified as a solution in the configuration, \
419
+ but does not have a @%s annotation."""
420
+ .formatted (solutionClass .getCanonicalName (), PlanningSolution .class .getSimpleName ()));
421
+ }
422
+ var parentSolutionAnnotation = solutionSuperclass .getAnnotation (PlanningSolution .class );
423
+ if (parentSolutionAnnotation == null ) {
424
+ throw new IllegalStateException ("""
425
+ The solutionClass (%s) has been specified as a solution in the configuration, \
426
+ but neither it nor its superclass (%s) have a @%s annotation."""
427
+ .formatted (solutionClass .getCanonicalName (), solutionSuperclass .getCanonicalName (),
428
+ PlanningSolution .class .getSimpleName ()));
429
+ }
430
+ return parentSolutionAnnotation ;
431
+ }
432
+
420
433
private void processValueRangeProviderAnnotation (DescriptorPolicy descriptorPolicy , Member member ) {
421
434
if (((AnnotatedElement ) member ).isAnnotationPresent (ValueRangeProvider .class )) {
422
435
var memberAccessor = descriptorPolicy .getMemberAccessorFactory ().buildAndCacheMemberAccessor (member ,
@@ -579,7 +592,7 @@ private void processProblemFactPropertyAnnotation(DescriptorPolicy descriptorPol
579
592
var type = memberAccessor .getType ();
580
593
if (!(Collection .class .isAssignableFrom (type ) || type .isArray ())) {
581
594
throw new IllegalStateException (
582
- "The solutionClass (%s) has a @%s annotated member (%s) that does not return a %s or an array."
595
+ "The solutionClass (%s) has a @%s- annotated member (%s) that does not return a %s or an array."
583
596
.formatted (solutionClass , ProblemFactCollectionProperty .class .getSimpleName (), member ,
584
597
Collection .class .getSimpleName ()));
585
598
}
@@ -596,16 +609,14 @@ private void processProblemFactPropertyAnnotation(DescriptorPolicy descriptorPol
596
609
throw new IllegalStateException ("Impossible situation with annotationClass (" + annotationClass + ")." );
597
610
}
598
611
if (problemFactType .isAnnotationPresent (PlanningEntity .class )) {
599
- throw new IllegalStateException (
600
- "The solutionClass (%s) has a @%s annotated member (%s) that returns a @%s. Maybe use @%s instead?"
601
- .formatted (
602
- solutionClass ,
603
- annotationClass ,
604
- memberAccessor .getName (),
605
- PlanningEntity .class .getSimpleName (),
606
- ((annotationClass == ProblemFactProperty .class )
607
- ? PlanningEntityProperty .class .getSimpleName ()
608
- : PlanningEntityCollectionProperty .class .getSimpleName ())));
612
+ throw new IllegalStateException ("""
613
+ The solutionClass (%s) has a @%s-annotated member (%s) that returns a @%s.
614
+ Maybe use @%s instead?"""
615
+ .formatted (solutionClass , annotationClass .getSimpleName (), memberAccessor .getName (),
616
+ PlanningEntity .class .getSimpleName (),
617
+ ((annotationClass == ProblemFactProperty .class )
618
+ ? PlanningEntityProperty .class .getSimpleName ()
619
+ : PlanningEntityCollectionProperty .class .getSimpleName ())));
609
620
}
610
621
}
611
622
0 commit comments