22
33import lombok .extern .slf4j .Slf4j ;
44
5- import java .lang .reflect .Array ;
65import java .util .*;
76import java .util .function .Supplier ;
87
8+ import static java .util .Collections .unmodifiableList ;
99import static java .util .Optional .ofNullable ;
10- import static org .bsc .langgraph4j .utils .CollectionsUtils .listOf ;
1110
1211
1312/**
2019@ Slf4j
2120public class AppenderChannel <T > implements Channel <List <T >> {
2221
22+ /**
23+ * A functional interface that is used to remove elements from a list.
24+ *
25+ * @param <T> the type of elements in the list
26+ */
27+ @ FunctionalInterface
28+ public interface RemoveIdentifier <T > {
29+ /**
30+ * Compares the specified element with the element at the given index.
31+ *
32+ * @param <T> the type of elements to compare
33+ * @param element the element to be compared
34+ * @param atIndex the index of the element to compare with
35+ * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.
36+ */
37+ int compareTo (T element , int atIndex );
38+ }
39+
2340 private final Reducer <List <T >> reducer ;
2441 private final Supplier <List <T >> defaultProvider ;
2542
@@ -58,16 +75,14 @@ public static <T> AppenderChannel<T> of( Supplier<List<T>> defaultProvider ) {
5875 /**
5976 * Constructs a new instance of {@code AppenderChannel} with the specified default provider.
6077 *
61- * @param <T> the type of elements in the lists to be processed
6278 * @param defaultProvider a supplier for the default list that will be used when no other list is available
6379 */
64- private AppenderChannel ( Supplier <List <T >> defaultProvider ) {
65- this .reducer = new Reducer <List < T > >() {
80+ private AppenderChannel ( Supplier <List <T >> defaultProvider ) {
81+ this .reducer = new Reducer <>() {
6682 /**
6783 * Combines two lists into one. If the first list is null, the second list is returned.
6884 * Otherwise, the second list is added to the end of the first list and the resulting list is returned.
6985 *
70- * @param <T> the type of elements in the lists
7186 * @param left the first list; may be null
7287 * @param right the second list
7388 * @return a new list containing all elements from both input lists
@@ -84,6 +99,77 @@ public List<T> apply(List<T> left, List<T> right) {
8499 this .defaultProvider = defaultProvider ;
85100 }
86101
102+ /**
103+ * This method removes elements from a given list based on the specified {@link RemoveIdentifier}.
104+ * It creates a copy of the original list, performs the removal operation, and returns an immutable view of the result.
105+ *
106+ * @param <T> The type of elements in the list.
107+ * @param list The list from which elements will be removed.
108+ * @param removeIdentifier An instance of {@link RemoveIdentifier} that defines how to identify elements for removal.
109+ * @return An unmodifiable view of the modified list with specified elements removed.
110+ */
111+ private List <T > remove (List <T > list , RemoveIdentifier <T > removeIdentifier ) {
112+ var result = new ArrayList <>(list );
113+ removeFromList (result , removeIdentifier );
114+ return unmodifiableList (result );
115+ }
116+
117+ /**
118+ * Removes an element from the list that matches the specified identifier.
119+ *
120+ * <p>This method iterates over the provided list and removes the first element for which the
121+ * {@link RemoveIdentifier#compareTo} method returns zero.</p>
122+ *
123+ * @param result the list to be modified
124+ * @param removeIdentifier the identifier used to find the element to remove
125+ */
126+ private void removeFromList (List <T > result , RemoveIdentifier <T > removeIdentifier ) {
127+ for ( int i = 0 ; i < result .size (); i ++ ) {
128+ if ( removeIdentifier .compareTo ( result .get (i ), i ) == 0 ) {
129+ result .remove (i );
130+ break ;
131+ }
132+ }
133+ }
134+
135+ /**
136+ * Represents a record for data removal operations with generic types.
137+ *
138+ * @param <T> the type of elements in the old values list
139+ */
140+ record RemoveData <T >( List <T > oldValues , List <?> newValues ) {
141+
142+ // copy constructor. make sure to copy the list to make them modifiable
143+ public RemoveData {
144+ oldValues = new ArrayList <>(oldValues );
145+ newValues = new ArrayList <>(newValues );
146+ }
147+ };
148+
149+ /**
150+ * Evaluates the removal of identifiers from the new values list and updates the RemoveData object accordingly.
151+ *
152+ * @param oldValues a {@code List} of old values
153+ * @param newValues a {@code List} of new values containing {@code RemoveIdentifier}s to be evaluated for removal
154+ * @return a {@literal RemoveData<T>} object with updated old and new values after removing identifiers
155+ */
156+ @ SuppressWarnings ("unchecked" )
157+ private RemoveData <T > evaluateRemoval (List <T > oldValues , List <?> newValues ) {
158+
159+ final var result = new RemoveData <>( oldValues , newValues );
160+
161+ newValues .stream ()
162+ .filter ( value -> value instanceof RemoveIdentifier <?> )
163+ .forEach ( value -> {
164+ result .newValues ().remove ( value );
165+ var removeIdentifier = (RemoveIdentifier <T >) value ;
166+ removeFromList ( result .oldValues (), removeIdentifier );
167+
168+ });
169+ return result ;
170+
171+ }
172+
87173 /**
88174 * Updates the value for a given key in the channel.
89175 *
@@ -95,28 +181,39 @@ public List<T> apply(List<T> left, List<T> right) {
95181 *
96182 * @throws UnsupportedOperationException If the channel does not support updates, typically due to an immutable list being used.
97183 */
184+ @ SuppressWarnings ("unchecked" )
98185 public Object update ( String key , Object oldValue , Object newValue ) {
99186
100187 if ( newValue == null ) {
101188 return oldValue ;
102189 }
190+
191+ boolean oldValueIsList = oldValue instanceof List <?>;
192+
103193 try {
194+ if ( oldValueIsList && newValue instanceof RemoveIdentifier <?> ) {
195+ return remove ( (List <T >)oldValue , (RemoveIdentifier <T >)newValue );
196+ }
104197 List <?> list = null ;
105198 if (newValue instanceof List ) {
106- list = (List <? >) newValue ;
199+ list = (List <Object >) newValue ;
107200 } else if (newValue .getClass ().isArray ()) {
108- list = Arrays .asList ((Object []) newValue );
201+ list = Arrays .asList ((T [])newValue );
109202 }
110203 if (list != null ) {
111204 if (list .isEmpty ()) {
112205 return oldValue ;
113206 }
207+ if ( oldValueIsList ) {
208+ var result = evaluateRemoval ( (List <T >)oldValue , list );
209+ return Channel .super .update (key , result .oldValues (), result .newValues ());
210+ }
114211 return Channel .super .update (key , oldValue , list );
115212 }
116213 // this is to allow single value other than List or Array
117214 try {
118215 T typedValue = (T )newValue ;
119- return Channel .super .update (key , oldValue , listOf (typedValue ));
216+ return Channel .super .update (key , oldValue , List . of (typedValue ));
120217 } catch (ClassCastException e ) {
121218 log .error ("Unsupported content type: {}" , newValue .getClass ());
122219 throw e ;
0 commit comments