8383import ai .timefold .solver .core .preview .api .domain .metamodel .PlanningSolutionMetaModel ;
8484import ai .timefold .solver .core .preview .api .domain .solution .diff .PlanningSolutionDiff ;
8585
86+ import org .jspecify .annotations .NonNull ;
8687import org .slf4j .Logger ;
8788import org .slf4j .LoggerFactory ;
8889
@@ -395,20 +396,9 @@ Maybe add a getScore() method with a @%s annotation."""
395396 }
396397
397398 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 ();
408400 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 ();
412402 if (solutionClonerClass != PlanningSolution .NullSolutionCloner .class ) {
413403 solutionCloner = ConfigUtils .newInstance (this ::toString , "solutionClonerClass" , solutionClonerClass );
414404 }
@@ -417,6 +407,29 @@ private void processSolutionAnnotations(DescriptorPolicy descriptorPolicy) {
417407 new LookUpStrategyResolver (descriptorPolicy , lookUpStrategyType );
418408 }
419409
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+
420433 private void processValueRangeProviderAnnotation (DescriptorPolicy descriptorPolicy , Member member ) {
421434 if (((AnnotatedElement ) member ).isAnnotationPresent (ValueRangeProvider .class )) {
422435 var memberAccessor = descriptorPolicy .getMemberAccessorFactory ().buildAndCacheMemberAccessor (member ,
@@ -579,7 +592,7 @@ private void processProblemFactPropertyAnnotation(DescriptorPolicy descriptorPol
579592 var type = memberAccessor .getType ();
580593 if (!(Collection .class .isAssignableFrom (type ) || type .isArray ())) {
581594 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."
583596 .formatted (solutionClass , ProblemFactCollectionProperty .class .getSimpleName (), member ,
584597 Collection .class .getSimpleName ()));
585598 }
@@ -596,16 +609,14 @@ private void processProblemFactPropertyAnnotation(DescriptorPolicy descriptorPol
596609 throw new IllegalStateException ("Impossible situation with annotationClass (" + annotationClass + ")." );
597610 }
598611 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 ())));
609620 }
610621 }
611622
0 commit comments