1
1
package crawlercommons .urlfrontier .service ;
2
2
3
-
3
+ import java . util . AbstractCollection ;
4
4
import java .util .AbstractMap ;
5
- import java .util .AbstractMap .SimpleImmutableEntry ;
6
- import java .util .ArrayList ;
5
+ import java .util .AbstractSet ;
7
6
import java .util .Collection ;
8
- import java .util .LinkedHashSet ;
9
- import java .util .List ;
7
+ import java .util .Iterator ;
10
8
import java .util .Map ;
11
9
import java .util .Objects ;
12
10
import java .util .Set ;
13
11
import java .util .concurrent .ConcurrentHashMap ;
14
12
import java .util .concurrent .ConcurrentSkipListMap ;
15
13
import java .util .concurrent .atomic .AtomicLong ;
16
14
import java .util .concurrent .locks .StampedLock ;
17
- import java .util .stream .Collectors ;
18
15
19
16
/**
20
17
* Concurrent version of LinkedHashMap Design goal is the same as for ConcurrentHashMap: Maintain
@@ -112,61 +109,77 @@ public V remove(Object key) {
112
109
113
110
@ Override
114
111
/**
115
- * Insertion order is preserved. The entry set returned is not backed up by the map.
112
+ * Insertion order is preserved.
116
113
*
117
114
* @return a linked hash set will all keys
118
115
*/
119
116
public Set <K > keySet () {
120
- // Return entries in insertion order
121
- long stamp = lock .tryOptimisticRead ();
122
-
123
- Set <K > orderedKeys = new LinkedHashSet <>(insertionOrderMap .values ());
124
-
125
- // Validate the read to ensure no concurrent modifications
126
- if (!lock .validate (stamp )) {
127
- stamp = lock .readLock ();
128
- try {
129
- orderedKeys = new LinkedHashSet <>(insertionOrderMap .values ());
130
- } finally {
131
- lock .unlockRead (stamp );
117
+ return new AbstractSet <>() {
118
+ @ Override
119
+ public Iterator <K > iterator () {
120
+ return new Iterator <>() {
121
+ final Iterator <Entry <Long , K >> orderedIterator =
122
+ insertionOrderMap .entrySet ().iterator ();
123
+
124
+ @ Override
125
+ public boolean hasNext () {
126
+ return orderedIterator .hasNext ();
127
+ }
128
+
129
+ @ Override
130
+ public K next () {
131
+ return orderedIterator .next ().getValue ();
132
+ }
133
+
134
+ @ Override
135
+ public void remove () {
136
+ throw new UnsupportedOperationException ();
137
+ }
138
+ };
132
139
}
133
- }
134
140
135
- return orderedKeys ;
141
+ @ Override
142
+ public int size () {
143
+ // Don't use size on CSLM as it's not in O(1) but rather O(n) and may be inacurate
144
+ return valueMap .size ();
145
+ }
146
+ };
136
147
}
137
148
138
149
/**
139
- * Insertion order is preserved. The entry set returned is not backed up by the map.
150
+ * Insertion order is preserved.
140
151
*
141
152
* @return a linked hash set will all entries
142
153
*/
143
154
@ Override
144
- public Set <Entry <K , V >> entrySet () {
145
- // Return entries in insertion order
146
- long stamp = lock .tryOptimisticRead ();
147
-
148
- Set <Entry <K , V >> orderedEntries =
149
- insertionOrderMap .values ().stream ()
150
- .map (key -> new SimpleImmutableEntry <>(key , valueMap .get (key ).value ))
151
- .collect (Collectors .toCollection (LinkedHashSet ::new ));
152
-
153
- // Validate the read to ensure no concurrent modifications
154
- if (!lock .validate (stamp )) {
155
- stamp = lock .readLock ();
156
- try {
157
- orderedEntries =
158
- insertionOrderMap .values ().stream ()
159
- .map (
160
- key ->
161
- new SimpleImmutableEntry <>(
162
- key , valueMap .get (key ).value ))
163
- .collect (Collectors .toCollection (LinkedHashSet ::new ));
164
- } finally {
165
- lock .unlockRead (stamp );
155
+ public Set <Map .Entry <K , V >> entrySet () {
156
+ return new AbstractSet <Map .Entry <K , V >>() {
157
+ @ Override
158
+ public Iterator <Map .Entry <K , V >> iterator () {
159
+ return new Iterator <Map .Entry <K , V >>() {
160
+ final Iterator <Entry <Long , K >> orderedIterator =
161
+ insertionOrderMap .entrySet ().iterator ();
162
+
163
+ @ Override
164
+ public boolean hasNext () {
165
+ return orderedIterator .hasNext ();
166
+ }
167
+
168
+ @ Override
169
+ public Map .Entry <K , V > next () {
170
+ Entry <Long , K > nextEntry = orderedIterator .next ();
171
+ K key = nextEntry .getValue ();
172
+ V value = valueMap .get (key ).value ;
173
+ return new AbstractMap .SimpleImmutableEntry <>(key , value );
174
+ }
175
+ };
166
176
}
167
- }
168
177
169
- return orderedEntries ;
178
+ @ Override
179
+ public int size () {
180
+ return valueMap .size ();
181
+ }
182
+ };
170
183
}
171
184
172
185
@ Override
@@ -238,14 +251,23 @@ public boolean remove(Object key, Object value) {
238
251
}
239
252
240
253
@ Override
241
- // FIXME: Should be atomic but stamped lock is not reentrant
242
254
public V replace (K key , V value ) {
243
255
244
256
long stamp = lock .writeLock ();
245
257
try {
246
258
if (valueMap .containsKey (key )) {
247
- return put (key , value );
248
- } else return null ;
259
+ ValueEntry vEntry = valueMap .get (key );
260
+ V oldValue = vEntry .value ;
261
+ vEntry .value = value ;
262
+
263
+ return oldValue ;
264
+ } else {
265
+ long newOrder = insertionCounter .getAndIncrement ();
266
+ insertionOrderMap .put (newOrder , key );
267
+ valueMap .put (key , new ValueEntry (value , newOrder ));
268
+
269
+ return null ;
270
+ }
249
271
} finally {
250
272
lock .unlockWrite (stamp );
251
273
}
@@ -254,7 +276,7 @@ public V replace(K key, V value) {
254
276
/*
255
277
* Returns the first entry according to insertion order
256
278
*/
257
- public Entry <K , V > firsEntry () {
279
+ public Entry <K , V > firstEntry () {
258
280
K key = insertionOrderMap .firstEntry ().getValue ();
259
281
260
282
return new AbstractMap .SimpleImmutableEntry <>(key , valueMap .get (key ).value );
@@ -304,36 +326,54 @@ public boolean containsValue(Object value) {
304
326
305
327
@ Override
306
328
public void putAll (Map <? extends K , ? extends V > m ) {
307
- // TODO Implement this optional operation
308
- throw new UnsupportedOperationException ();
329
+
330
+ long stamp = lock .writeLock ();
331
+ try {
332
+ for (Entry <? extends K , ? extends V > entry : m .entrySet ()) {
333
+ K key = entry .getKey ();
334
+
335
+ // Check if key already exists
336
+ ValueEntry ventry = valueMap .get (key );
337
+ if (ventry != null ) {
338
+ ventry .value = entry .getValue ();
339
+ } else {
340
+ long newOrder = insertionCounter .getAndIncrement ();
341
+ insertionOrderMap .put (newOrder , key );
342
+ valueMap .put (key , new ValueEntry (entry .getValue (), newOrder ));
343
+ }
344
+ }
345
+ } finally {
346
+ lock .unlock (stamp );
347
+ }
309
348
}
310
349
311
350
/** Returns a Collection view of the values contained in this map in insertion order. */
312
351
@ Override
313
352
public Collection <V > values () {
314
- List <V > values ;
315
-
316
- // Return entries in insertion order
317
- long stamp = lock .tryOptimisticRead ();
318
-
319
- values =
320
- insertionOrderMap .values ().stream ()
321
- .map (key -> valueMap .get (key ).value )
322
- .collect (Collectors .toCollection (ArrayList ::new ));
323
-
324
- // Validate the read to ensure no concurrent modifications
325
- if (!lock .validate (stamp )) {
326
- stamp = lock .readLock ();
327
- try {
328
- values =
329
- insertionOrderMap .values ().stream ()
330
- .map (key -> valueMap .get (key ).value )
331
- .collect (Collectors .toCollection (ArrayList ::new ));
332
- } finally {
333
- lock .unlockRead (stamp );
353
+ return new AbstractCollection <>() {
354
+ @ Override
355
+ public Iterator <V > iterator () {
356
+ return new Iterator <>() {
357
+ final Iterator <Entry <Long , K >> orderedIterator =
358
+ insertionOrderMap .entrySet ().iterator ();
359
+
360
+ @ Override
361
+ public boolean hasNext () {
362
+ return orderedIterator .hasNext ();
363
+ }
364
+
365
+ @ Override
366
+ public V next () {
367
+ K key = orderedIterator .next ().getValue ();
368
+ return valueMap .get (key ).value ;
369
+ }
370
+ };
334
371
}
335
- }
336
372
337
- return values ;
373
+ @ Override
374
+ public int size () {
375
+ return valueMap .size ();
376
+ }
377
+ };
338
378
}
339
379
}
0 commit comments