22
33import java .util .List ;
44import java .util .Map ;
5+ import java .util .function .Consumer ;
56
67import ai .timefold .solver .core .impl .domain .variable .descriptor .ListVariableDescriptor ;
78import ai .timefold .solver .core .impl .domain .variable .index .IndexShadowVariableDescriptor ;
1617final class ListVariableState <Solution_ > {
1718
1819 private final ListVariableDescriptor <Solution_ > sourceVariableDescriptor ;
20+ private final Consumer <Object > notifier ;
1921
2022 private ExternalizedIndexVariableProcessor <Solution_ > externalizedIndexProcessor = null ;
2123 private ExternalizedListInverseVariableProcessor <Solution_ > externalizedInverseProcessor = null ;
@@ -27,8 +29,10 @@ final class ListVariableState<Solution_> {
2729 private int unassignedCount = 0 ;
2830 private Map <Object , MutablePosition > elementPositionMap ;
2931
30- public ListVariableState (ListVariableDescriptor <Solution_ > sourceVariableDescriptor ) {
32+ public ListVariableState (ListVariableDescriptor <Solution_ > sourceVariableDescriptor ,
33+ Consumer <Object > notifier ) {
3134 this .sourceVariableDescriptor = sourceVariableDescriptor ;
35+ this .notifier = notifier ;
3236 }
3337
3438 public void linkShadowVariable (IndexShadowVariableDescriptor <Solution_ > shadowVariableDescriptor ) {
@@ -121,49 +125,26 @@ public void addElement(Object entity, List<Object> elements, Object element, int
121125 .formatted (sourceVariableDescriptor , element , index , oldPosition ));
122126 }
123127 }
128+ var elementUpdateSent = false ;
124129 if (externalizedIndexProcessor != null ) {
125- externalizedIndexProcessor .addElement (scoreDirector , element , index );
130+ elementUpdateSent = externalizedIndexProcessor .addElement (scoreDirector , element , index );
126131 }
127132 if (externalizedInverseProcessor != null ) {
128- externalizedInverseProcessor .addElement (scoreDirector , entity , element );
133+ elementUpdateSent = externalizedInverseProcessor .addElement (scoreDirector , entity , element ) || elementUpdateSent ;
129134 }
130135 if (externalizedPreviousElementProcessor != null ) {
131- externalizedPreviousElementProcessor .setElement (scoreDirector , elements , element , index );
136+ elementUpdateSent = externalizedPreviousElementProcessor .setElement (scoreDirector , elements , element , index )
137+ || elementUpdateSent ;
132138 }
133139 if (externalizedNextElementProcessor != null ) {
134- externalizedNextElementProcessor .setElement (scoreDirector , elements , element , index );
140+ elementUpdateSent =
141+ externalizedNextElementProcessor .setElement (scoreDirector , elements , element , index ) || elementUpdateSent ;
135142 }
136143 unassignedCount --;
137- }
138-
139- public void removeElement (Object entity , Object element , int index ) {
140- if (requiresPositionMap ) {
141- var oldPosition = elementPositionMap .remove (element );
142- if (oldPosition == null ) {
143- throw new IllegalStateException (
144- "The supply for list variable (%s) is corrupted, because the element (%s) at index (%d) was already unassigned (%s)."
145- .formatted (sourceVariableDescriptor , element , index , oldPosition ));
146- }
147- var oldIndex = oldPosition .getIndex ();
148- if (oldIndex != index ) {
149- throw new IllegalStateException (
150- "The supply for list variable (%s) is corrupted, because the element (%s) at index (%d) had an old index (%d) which is not the current index (%d)."
151- .formatted (sourceVariableDescriptor , element , index , oldIndex , index ));
152- }
144+ // Trigger notifier if none of the previous methods triggered a shadow var update for this element.
145+ if (!elementUpdateSent ) {
146+ notifier .accept (element );
153147 }
154- if (externalizedIndexProcessor != null ) {
155- externalizedIndexProcessor .removeElement (scoreDirector , element );
156- }
157- if (externalizedInverseProcessor != null ) {
158- externalizedInverseProcessor .removeElement (scoreDirector , entity , element );
159- }
160- if (externalizedPreviousElementProcessor != null ) {
161- externalizedPreviousElementProcessor .unsetElement (scoreDirector , element );
162- }
163- if (externalizedNextElementProcessor != null ) {
164- externalizedNextElementProcessor .unsetElement (scoreDirector , element );
165- }
166- unassignedCount ++;
167148 }
168149
169150 public void unassignElement (Object element ) {
@@ -175,37 +156,49 @@ public void unassignElement(Object element) {
175156 .formatted (sourceVariableDescriptor , element ));
176157 }
177158 }
159+ var elementUpdateSent = false ;
178160 if (externalizedIndexProcessor != null ) {
179- externalizedIndexProcessor .unassignElement (scoreDirector , element );
161+ elementUpdateSent = externalizedIndexProcessor .unassignElement (scoreDirector , element );
180162 }
181163 if (externalizedInverseProcessor != null ) {
182- externalizedInverseProcessor .unassignElement (scoreDirector , element );
164+ elementUpdateSent = externalizedInverseProcessor .unassignElement (scoreDirector , element ) || elementUpdateSent ;
183165 }
184166 if (externalizedPreviousElementProcessor != null ) {
185- externalizedPreviousElementProcessor .unsetElement (scoreDirector , element );
167+ elementUpdateSent = externalizedPreviousElementProcessor .unsetElement (scoreDirector , element ) || elementUpdateSent ;
186168 }
187169 if (externalizedNextElementProcessor != null ) {
188- externalizedNextElementProcessor .unsetElement (scoreDirector , element );
170+ elementUpdateSent = externalizedNextElementProcessor .unsetElement (scoreDirector , element ) || elementUpdateSent ;
189171 }
190172 unassignedCount ++;
173+ // Trigger notifier if none of the previous methods triggered a shadow var update for this element.
174+ if (!elementUpdateSent ) {
175+ notifier .accept (element );
176+ }
191177 }
192178
193179 public boolean changeElement (Object entity , List <Object > elements , int index ) {
194180 var element = elements .get (index );
195181 var difference = processElementPosition (entity , element , index );
182+ var elementUpdateSent = false ;
196183 if (difference .indexChanged && externalizedIndexProcessor != null ) {
197- externalizedIndexProcessor .changeElement (scoreDirector , element , index );
184+ elementUpdateSent = externalizedIndexProcessor .changeElement (scoreDirector , element , index );
198185 }
199186 if (difference .entityChanged && externalizedInverseProcessor != null ) {
200- externalizedInverseProcessor .changeElement (scoreDirector , entity , element );
187+ elementUpdateSent = externalizedInverseProcessor .changeElement (scoreDirector , entity , element ) || elementUpdateSent ;
201188 }
202189 // Next and previous still might have changed, even if the index and entity did not.
203190 // Those are based on what happened elsewhere in the list.
204191 if (externalizedPreviousElementProcessor != null ) {
205- externalizedPreviousElementProcessor .setElement (scoreDirector , elements , element , index );
192+ elementUpdateSent = externalizedPreviousElementProcessor .setElement (scoreDirector , elements , element , index )
193+ || elementUpdateSent ;
206194 }
207195 if (externalizedNextElementProcessor != null ) {
208- externalizedNextElementProcessor .setElement (scoreDirector , elements , element , index );
196+ elementUpdateSent =
197+ externalizedNextElementProcessor .setElement (scoreDirector , elements , element , index ) || elementUpdateSent ;
198+ }
199+ // Trigger notifier if none of the previous methods triggered a shadow var update for this element.
200+ if (!elementUpdateSent ) {
201+ notifier .accept (element );
209202 }
210203 return difference .anythingChanged ;
211204 }
0 commit comments