2
2
3
3
import lombok .extern .slf4j .Slf4j ;
4
4
5
- import java .lang .reflect .Array ;
6
5
import java .util .*;
7
6
import java .util .function .Supplier ;
8
7
8
+ import static java .util .Collections .unmodifiableList ;
9
9
import static java .util .Optional .ofNullable ;
10
- import static org .bsc .langgraph4j .utils .CollectionsUtils .listOf ;
11
10
12
11
13
12
/**
20
19
@ Slf4j
21
20
public class AppenderChannel <T > implements Channel <List <T >> {
22
21
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
+
23
40
private final Reducer <List <T >> reducer ;
24
41
private final Supplier <List <T >> defaultProvider ;
25
42
@@ -58,16 +75,14 @@ public static <T> AppenderChannel<T> of( Supplier<List<T>> defaultProvider ) {
58
75
/**
59
76
* Constructs a new instance of {@code AppenderChannel} with the specified default provider.
60
77
*
61
- * @param <T> the type of elements in the lists to be processed
62
78
* @param defaultProvider a supplier for the default list that will be used when no other list is available
63
79
*/
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 <>() {
66
82
/**
67
83
* Combines two lists into one. If the first list is null, the second list is returned.
68
84
* Otherwise, the second list is added to the end of the first list and the resulting list is returned.
69
85
*
70
- * @param <T> the type of elements in the lists
71
86
* @param left the first list; may be null
72
87
* @param right the second list
73
88
* @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) {
84
99
this .defaultProvider = defaultProvider ;
85
100
}
86
101
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
+
87
173
/**
88
174
* Updates the value for a given key in the channel.
89
175
*
@@ -95,28 +181,39 @@ public List<T> apply(List<T> left, List<T> right) {
95
181
*
96
182
* @throws UnsupportedOperationException If the channel does not support updates, typically due to an immutable list being used.
97
183
*/
184
+ @ SuppressWarnings ("unchecked" )
98
185
public Object update ( String key , Object oldValue , Object newValue ) {
99
186
100
187
if ( newValue == null ) {
101
188
return oldValue ;
102
189
}
190
+
191
+ boolean oldValueIsList = oldValue instanceof List <?>;
192
+
103
193
try {
194
+ if ( oldValueIsList && newValue instanceof RemoveIdentifier <?> ) {
195
+ return remove ( (List <T >)oldValue , (RemoveIdentifier <T >)newValue );
196
+ }
104
197
List <?> list = null ;
105
198
if (newValue instanceof List ) {
106
- list = (List <? >) newValue ;
199
+ list = (List <Object >) newValue ;
107
200
} else if (newValue .getClass ().isArray ()) {
108
- list = Arrays .asList ((Object []) newValue );
201
+ list = Arrays .asList ((T [])newValue );
109
202
}
110
203
if (list != null ) {
111
204
if (list .isEmpty ()) {
112
205
return oldValue ;
113
206
}
207
+ if ( oldValueIsList ) {
208
+ var result = evaluateRemoval ( (List <T >)oldValue , list );
209
+ return Channel .super .update (key , result .oldValues (), result .newValues ());
210
+ }
114
211
return Channel .super .update (key , oldValue , list );
115
212
}
116
213
// this is to allow single value other than List or Array
117
214
try {
118
215
T typedValue = (T )newValue ;
119
- return Channel .super .update (key , oldValue , listOf (typedValue ));
216
+ return Channel .super .update (key , oldValue , List . of (typedValue ));
120
217
} catch (ClassCastException e ) {
121
218
log .error ("Unsupported content type: {}" , newValue .getClass ());
122
219
throw e ;
0 commit comments