2121import java .util .Map ;
2222import java .util .function .BiPredicate ;
2323import java .util .function .Consumer ;
24- import java .util .function .Predicate ;
2524
2625import ai .timefold .solver .core .api .domain .entity .PlanningEntity ;
2726import ai .timefold .solver .core .api .domain .entity .PlanningPin ;
6463import ai .timefold .solver .core .preview .api .domain .metamodel .PlanningEntityMetaModel ;
6564import ai .timefold .solver .core .preview .api .neighborhood .stream .function .UniNeighborhoodsPredicate ;
6665
67- import org .jspecify .annotations .NonNull ;
6866import org .jspecify .annotations .Nullable ;
6967import org .slf4j .Logger ;
7068import org .slf4j .LoggerFactory ;
@@ -92,7 +90,6 @@ public class EntityDescriptor<Solution_> {
9290 private final SolutionDescriptor <Solution_ > solutionDescriptor ;
9391 private final Class <?> entityClass ;
9492 private final List <Class <?>> declaredInheritedEntityClassList = new ArrayList <>();
95- private final Predicate <Object > isInitializedPredicate ;
9693 private final List <MemberAccessor > declaredPlanningPinIndexMemberAccessorList = new ArrayList <>();
9794 @ Nullable
9895 private ShadowVariablesInconsistentVariableDescriptor <Solution_ > shadowVariablesInconsistentDescriptor ;
@@ -142,17 +139,11 @@ public EntityDescriptor(int ordinal, SolutionDescriptor<Solution_> solutionDescr
142139 this .solutionDescriptor = solutionDescriptor ;
143140 this .entityClass = entityClass ;
144141 this .declaredInheritedEntityClassList .addAll (extractInheritedClasses (entityClass ));
145- isInitializedPredicate = this ::isInitialized ;
146142 if (entityClass .getPackage () == null ) {
147- LOGGER .warn ("The entityClass ({}) should be in a proper java package." , entityClass );
143+ LOGGER .warn ("The entityClass ({}) is in an unnamed package." , entityClass );
148144 }
149145 }
150146
151- @ SuppressWarnings ("unchecked" )
152- public static Collection <Class <? extends Annotation >> getVariableAnnotationClasses () {
153- return List .of (VARIABLE_ANNOTATION_CLASSES );
154- }
155-
156147 /**
157148 * A number unique within a {@link SolutionDescriptor}, increasing sequentially from zero.
158149 * Used for indexing in arrays to avoid object hash lookups in maps.
@@ -163,18 +154,6 @@ public int getOrdinal() {
163154 return ordinal ;
164155 }
165156
166- /**
167- * Using entityDescriptor::isInitialized directly breaks node sharing
168- * because it creates multiple instances of this {@link Predicate}.
169- *
170- * @deprecated Prefer {@link #getEntityForEachFilter()} ()}.
171- * @return never null, always the same {@link Predicate} instance to {@link #isInitialized(Object)}
172- */
173- @ Deprecated (forRemoval = true )
174- public Predicate <Object > getIsInitializedPredicate () {
175- return isInitializedPredicate ;
176- }
177-
178157 public EntityForEachFilter getEntityForEachFilter () {
179158 if (entityForEachFilter == null ) {
180159 entityForEachFilter = new EntityForEachFilter (this );
@@ -206,7 +185,7 @@ public void processAnnotations(DescriptorPolicy descriptorPolicy) {
206185 && declaredInheritedEntityClassList .isEmpty ()) {
207186 throw new IllegalStateException (
208187 "The entityClass (%s) should have at least 1 getter method or 1 field with a %s annotation or a shadow variable annotation."
209- .formatted (entityClass , PlanningVariable .class .getSimpleName ()));
188+ .formatted (entityClass . getCanonicalName () , PlanningVariable .class .getSimpleName ()));
210189 }
211190 processVariableAnnotations (descriptorPolicy );
212191 }
@@ -219,7 +198,7 @@ private void processEntityAnnotations() {
219198 if (entityAnnotation == null && declaredInheritedEntityClassList .isEmpty ()) {
220199 throw new IllegalStateException (
221200 "The entityClass (%s) has been specified as a planning entity in the configuration, but does not have a @%s annotation."
222- .formatted (entityClass , PlanningEntity .class .getSimpleName ()));
201+ .formatted (entityClass . getCanonicalName () , PlanningEntity .class .getSimpleName ()));
223202 }
224203 // We use the parent class of the entity as the base annotation
225204 if (entityAnnotation == null ) {
@@ -250,7 +229,8 @@ private void processSorting(PlanningEntity entityAnnotation) {
250229 if (comparatorClass != null && comparatorFactoryClass != null ) {
251230 throw new IllegalStateException (
252231 "The entityClass (%s) cannot have a comparatorClass (%s) and a comparatorFactoryClass (%s) at the same time."
253- .formatted (entityClass , comparatorClass .getName (), comparatorFactoryClass .getName ()));
232+ .formatted (entityClass .getCanonicalName (), comparatorClass .getName (),
233+ comparatorFactoryClass .getName ()));
254234 }
255235 if (comparatorClass != null ) {
256236 var comparator = ConfigUtils .newInstance (this ::toString , "comparatorClass" , comparatorClass );
@@ -319,12 +299,13 @@ private void registerVariableAccessor(int nextVariableDescriptorOrdinal,
319299 throw new IllegalStateException ("""
320300 The entityClass (%s) has a @%s annotated member (%s), duplicated by member for variableDescriptor (%s).
321301 Maybe the annotation is defined on both the field and its getter."""
322- .formatted (entityClass , variableAnnotationClass .getSimpleName (), memberAccessor , duplicate ));
302+ .formatted (entityClass .getCanonicalName (), variableAnnotationClass .getSimpleName (), memberAccessor ,
303+ duplicate ));
323304 } else if (variableAnnotationClass .equals (PlanningVariable .class )) {
324305 var type = memberAccessor .getType ();
325306 if (type .isArray ()) {
326307 throw new IllegalStateException ("The entityClass (%s) has a @%s annotated member (%s) that is of an array type."
327- .formatted (entityClass , PlanningVariable .class .getSimpleName (), memberAccessor ));
308+ .formatted (entityClass . getCanonicalName () , PlanningVariable .class .getSimpleName (), memberAccessor ));
328309 }
329310 var variableDescriptor = new BasicVariableDescriptor <>(nextVariableDescriptorOrdinal , this , memberAccessor );
330311 declaredGenuineVariableDescriptorMap .put (memberName , variableDescriptor );
@@ -336,7 +317,7 @@ The entityClass (%s) has a @%s annotated member (%s), duplicated by member for v
336317 throw new IllegalStateException ("""
337318 The entityClass (%s) has a @%s annotated member (%s) that has an unsupported type (%s).
338319 Maybe use %s."""
339- .formatted (entityClass , PlanningListVariable .class .getSimpleName (), memberAccessor ,
320+ .formatted (entityClass . getCanonicalName () , PlanningListVariable .class .getSimpleName (), memberAccessor ,
340321 memberAccessor .getType (), List .class .getCanonicalName ()));
341322 }
342323 } else if (variableAnnotationClass .equals (InverseRelationShadowVariable .class )) {
@@ -392,7 +373,7 @@ private void processPlanningPinAnnotation(DescriptorPolicy descriptorPolicy, Mem
392373 if (!Boolean .TYPE .isAssignableFrom (type ) && !Boolean .class .isAssignableFrom (type )) {
393374 throw new IllegalStateException (
394375 "The entityClass (%s) has a %s annotated member (%s) that is not a boolean or Boolean."
395- .formatted (entityClass , PlanningPin .class .getSimpleName (), member ));
376+ .formatted (entityClass . getCanonicalName () , PlanningPin .class .getSimpleName (), member ));
396377 }
397378 declaredPinEntityFilterList .add (new PinEntityFilter <>(memberAccessor ));
398379 }
@@ -404,7 +385,7 @@ private void processPlanningPinIndexAnnotation(DescriptorPolicy descriptorPolicy
404385 if (!hasAnyGenuineListVariables ()) {
405386 throw new IllegalStateException (
406387 "The entityClass (%s) has a %s annotated member (%s) but no %s annotated member."
407- .formatted (entityClass , PlanningPinToIndex .class .getSimpleName (), member ,
388+ .formatted (entityClass . getCanonicalName () , PlanningPinToIndex .class .getSimpleName (), member ,
408389 PlanningListVariable .class .getSimpleName ()));
409390 }
410391 var memberAccessor = descriptorPolicy .getMemberAccessorFactory ().buildAndCacheMemberAccessor (member ,
@@ -413,7 +394,7 @@ private void processPlanningPinIndexAnnotation(DescriptorPolicy descriptorPolicy
413394 if (!Integer .TYPE .isAssignableFrom (type )) {
414395 throw new IllegalStateException (
415396 "The entityClass (%s) has a %s annotated member (%s) that is not a primitive int."
416- .formatted (entityClass , PlanningPinToIndex .class .getSimpleName (), member ));
397+ .formatted (entityClass . getCanonicalName () , PlanningPinToIndex .class .getSimpleName (), member ));
417398 }
418399 declaredPlanningPinIndexMemberAccessorList .add (memberAccessor );
419400 }
@@ -469,20 +450,19 @@ private void createEffectiveVariableDescriptorMaps() {
469450 .toList ();
470451 if (!redefinedGenuineVariables .isEmpty ()) {
471452 throw new IllegalStateException ("""
472- The class (%s) redefines the genuine variables (%s), which is not permitted.
473- Maybe remove the variables (%s) from the class (%s).""" .formatted (entityClass ,
474- redefinedGenuineVariables , String .join (", " , redefinedGenuineVariables ),
475- entityClass ));
453+ The entityClass (%s) redefines the genuine variables (%s), which is not permitted.
454+ Maybe remove the variables (%s) from the class.""" .formatted (entityClass .getCanonicalName (),
455+ redefinedGenuineVariables , String .join (", " , redefinedGenuineVariables )));
476456 }
477457 effectiveGenuineVariableDescriptorMap .putAll (declaredGenuineVariableDescriptorMap );
478458 var redefinedShadowVariables = declaredShadowVariableDescriptorMap .keySet ().stream ()
479459 .filter (key -> effectiveShadowVariableDescriptorMap .containsKey (key ))
480460 .toList ();
481461 if (!redefinedShadowVariables .isEmpty ()) {
482462 throw new IllegalStateException ("""
483- The class (%s) redefines the shadow variables (%s), which is not permitted.
484- Maybe remove the variables (%s) from the class (%s) .""" .formatted (entityClass ,
485- redefinedShadowVariables , redefinedShadowVariables , entityClass ));
463+ The entityClass (%s) redefines the shadow variables (%s), which is not permitted.
464+ Maybe remove the variables (%s) from the class.""" .formatted (entityClass . getCanonicalName () ,
465+ redefinedShadowVariables , redefinedShadowVariables ));
486466 }
487467 effectiveShadowVariableDescriptorMap .putAll (declaredShadowVariableDescriptorMap );
488468
@@ -536,7 +516,7 @@ private void createEffectivePlanningPinIndexReader() {
536516 }
537517 default -> throw new IllegalStateException (
538518 "The entityClass (%s) has (%d) @%s-annotated members (%s), where it should only have one."
539- .formatted (entityClass , planningPinIndexMemberAccessorList .size (),
519+ .formatted (entityClass . getCanonicalName () , planningPinIndexMemberAccessorList .size (),
540520 PlanningPinToIndex .class .getSimpleName (), planningPinIndexMemberAccessorList ));
541521 }
542522 }
@@ -690,18 +670,6 @@ public boolean hasVariableDescriptor(String variableName) {
690670 return effectiveVariableDescriptorMap .get (variableName );
691671 }
692672
693- public @ NonNull VariableDescriptor <Solution_ > getVariableDescriptorOrFail (String variableName ) {
694- var variableDescriptor = effectiveVariableDescriptorMap .get (variableName );
695- if (variableDescriptor == null ) {
696- throw new IllegalArgumentException ("""
697- Entity class %s does not hava a "%s" genuine or shadow variable.
698- Maybe you meant one of %s?"""
699- .formatted (entityClass .getSimpleName (),
700- variableName , effectiveVariableDescriptorMap .keySet ()));
701- }
702- return variableDescriptor ;
703- }
704-
705673 public boolean hasAnyDeclaredGenuineVariableDescriptor () {
706674 return !declaredGenuineVariableDescriptorMap .isEmpty ();
707675 }
@@ -734,7 +702,7 @@ public String buildInvalidVariableNameExceptionMessage(String variableName) {
734702 """
735703 The variableName (%s) for entityClass (%s) does not exist as a getter or field on that class.
736704 Check the spelling of the variableName (%s)."""
737- .formatted (variableName , entityClass , variableName );
705+ .formatted (variableName , entityClass . getCanonicalName () , variableName );
738706 if (variableName .length () >= 2
739707 && !Character .isUpperCase (variableName .charAt (0 ))
740708 && Character .isUpperCase (variableName .charAt (1 ))) {
@@ -748,7 +716,7 @@ Check the spelling of the variableName (%s)."""
748716 return """
749717 The variableName (%s) for entityClass (%s) exists as a getter or field on that class, but isn't in the planning variables (%s).
750718 %sMaybe your planning entity's getter or field lacks a @%s annotation or a shadow variable annotation."""
751- .formatted (variableName , entityClass , effectiveVariableDescriptorMap .keySet (),
719+ .formatted (variableName , entityClass . getCanonicalName () , effectiveVariableDescriptorMap .keySet (),
752720 Character .isUpperCase (variableName .charAt (0 ))
753721 ? "Maybe the variableName (%s) should start with a lowercase.%n" .formatted (variableName )
754722 : "" ,
@@ -856,7 +824,7 @@ public boolean isMovable(Solution_ workingSolution, Object entity) {
856824
857825 @ Override
858826 public String toString () {
859- return "%s(%s)" .formatted (getClass ().getSimpleName (), entityClass .getName ());
827+ return "%s(%s)" .formatted (getClass ().getSimpleName (), entityClass .getCanonicalName ());
860828 }
861829
862830}
0 commit comments