1   /**
2    * Copyright 2008 Atlassian Pty Ltd 
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License"); 
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at 
7    * 
8    *     http://www.apache.org/licenses/LICENSE-2.0 
9    * 
10   * Unless required by applicable law or agreed to in writing, software 
11   * distributed under the License is distributed on an "AS IS" BASIS, 
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
13   * See the License for the specific language governing permissions and 
14   * limitations under the License.
15   */
16  
17  package com.atlassian.util.concurrent;
18  
19  import static com.atlassian.util.concurrent.TestUtil.serialize;
20  import static java.util.Arrays.asList;
21  import static org.junit.Assert.assertArrayEquals;
22  import static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertFalse;
24  import static org.junit.Assert.assertNotNull;
25  import static org.junit.Assert.assertNull;
26  import static org.junit.Assert.assertTrue;
27  import static org.junit.Assert.fail;
28  
29  import com.atlassian.util.concurrent.AbstractCopyOnWriteMap.View;
30  import com.atlassian.util.concurrent.MapBuilder.E;
31  
32  import org.junit.Test;
33  
34  import java.util.ArrayList;
35  import java.util.Collection;
36  import java.util.Collections;
37  import java.util.HashMap;
38  import java.util.Iterator;
39  import java.util.Map;
40  import java.util.Map.Entry;
41  import java.util.concurrent.ConcurrentMap;
42  import java.util.concurrent.atomic.AtomicInteger;
43  import java.util.concurrent.atomic.AtomicReference;
44  
45  public class CopyOnWriteMapTest {
46  
47      @Test
48      public void factoryCalledOnConstructor() {
49          final AtomicInteger count = new AtomicInteger();
50          final Map<String, String> init = MapBuilder.build("1", "o1", "2", "o2", "3", "o3");
51          final Map<String, String> map = new CopyOnWriteMap<String, String>(init, View.Type.STABLE) {
52              private static final long serialVersionUID = 8866224559807093002L;
53  
54              @Override
55              public <N extends Map<? extends String, ? extends String>> Map<String, String> copy(final N map) {
56                  count.getAndIncrement();
57                  return new HashMap<String, String>(map);
58              }
59          };
60          assertEquals(1, count.get());
61          assertEquals(3, map.size());
62          assertTrue(map.containsKey("2"));
63          assertTrue(map.containsValue("o3"));
64          assertEquals("o1", map.get("1"));
65      }
66  
67      @Test
68      public void factoryCalledOnWrite() {
69          final AtomicInteger count = new AtomicInteger();
70          final Map<String, String> map = new CopyOnWriteMap<String, String>(View.Type.STABLE) {
71              private static final long serialVersionUID = -3858713272422952372L;
72  
73              @Override
74              public <N extends Map<? extends String, ? extends String>> Map<String, String> copy(final N map) {
75                  count.getAndIncrement();
76                  return new HashMap<String, String>(map);
77              }
78          };
79  
80          assertEquals("should be called in ctor", 1, count.get());
81          map.put("test", "test");
82          assertEquals("should be called in put", 2, count.get());
83          assertEquals(1, map.size());
84          assertTrue(map.containsKey("test"));
85          assertTrue(map.containsValue("test"));
86          assertEquals("should not be called in reads", 2, count.get());
87          map.putAll(MapBuilder.build("1", "test1", "2", "test2", "3", "test3"));
88          assertEquals("should be called in putAll", 3, count.get());
89          assertEquals(4, map.size());
90          assertTrue(map.containsKey("1"));
91          assertTrue(map.containsValue("test3"));
92          map.remove("2");
93          assertEquals("should be called in remove", 4, count.get());
94          assertEquals(3, map.size());
95          assertFalse(map.containsValue("test2"));
96          map.clear();
97          assertEquals("should be called in clear", 5, count.get());
98          assertEquals(0, map.size());
99      }
100 
101     @Test
102     public void delegateHashMap() throws Exception {
103         final Map<String, String> map = MapBuilder.<String, String> builder().add("key", "value").toMap();
104         final CopyOnWriteMap<String, String> cowMap = CopyOnWriteMap.<String, String> builder().addAll(map).newHashMap();
105         assertEquals(map, cowMap);
106         assertEquals(map.hashCode(), cowMap.hashCode());
107         assertEquals(map.toString(), cowMap.toString());
108     }
109 
110     @Test
111     public void delegateKeySet() throws Exception {
112         final Map<String, String> map = MapBuilder.<String, String> builder().add("key", "value").toMap();
113         final CopyOnWriteMap<String, String> cowMap = CopyOnWriteMap.<String, String> builder().addAll(map).newHashMap();
114         assertEquals(map.keySet(), cowMap.keySet());
115         assertEquals(map.keySet().hashCode(), cowMap.keySet().hashCode());
116         assertEquals(map.keySet().toString(), cowMap.keySet().toString());
117     }
118 
119     @Test
120     public void delegateEqualityValues() throws Exception {
121         final Map<String, String> map = MapBuilder.<String, String> builder().add("key", "value").toMap();
122         final CopyOnWriteMap<String, String> cowMap = CopyOnWriteMap.<String, String> builder().addAll(map).newHashMap();
123         assertEquals(new ArrayList<String>(map.values()), new ArrayList<String>(cowMap.values()));
124         assertEquals(new ArrayList<String>(map.values()).hashCode(), new ArrayList<String>(cowMap.values()).hashCode());
125         assertEquals(map.values().toString(), cowMap.values().toString());
126     }
127 
128     @Test
129     public void delegateEntrySet() throws Exception {
130         final Map<String, String> map = MapBuilder.<String, String> builder().add("key", "value").toMap();
131         final CopyOnWriteMap<String, String> cowMap = CopyOnWriteMap.<String, String> builder().addAll(map).newHashMap();
132         assertEquals(map.entrySet(), cowMap.entrySet());
133         assertEquals(map.entrySet().hashCode(), cowMap.entrySet().hashCode());
134         assertEquals(map.entrySet().toString(), cowMap.entrySet().toString());
135     }
136 
137     @Test
138     public void delegateLinked() throws Exception {
139         final Map<String, String> map = MapBuilder.<String, String> builder().add("key", "value").toMap();
140         final CopyOnWriteMap<String, String> cowMap = CopyOnWriteMap.<String, String> builder().addAll(map).newLinkedMap();
141         assertEquals(map, cowMap);
142         assertEquals(map.hashCode(), cowMap.hashCode());
143         assertEquals(map.toString(), cowMap.toString());
144     }
145 
146     @Test
147     public void delegateKeySetLinked() throws Exception {
148         final Map<String, String> map = MapBuilder.<String, String> builder().add("key", "value").toMap();
149         final CopyOnWriteMap<String, String> cowMap = CopyOnWriteMap.<String, String> builder().addAll(map).newLinkedMap();
150         assertEquals(map.keySet(), cowMap.keySet());
151         assertEquals(map.keySet().hashCode(), cowMap.keySet().hashCode());
152         assertEquals(map.keySet().toString(), cowMap.keySet().toString());
153     }
154 
155     @Test
156     public void delegateValuesLinked() throws Exception {
157         final Map<String, String> map = MapBuilder.<String, String> builder().add("key", "value").toMap();
158         final CopyOnWriteMap<String, String> cowMap = CopyOnWriteMap.<String, String> builder().addAll(map).newLinkedMap();
159         assertEquals(new ArrayList<String>(map.values()), new ArrayList<String>(cowMap.values()));
160         assertEquals(new ArrayList<String>(map.values()).hashCode(), new ArrayList<String>(cowMap.values()).hashCode());
161         assertEquals(map.values().toString(), cowMap.values().toString());
162     }
163 
164     @Test
165     public void delegateEntrySetLinked() throws Exception {
166         final Map<String, String> map = MapBuilder.<String, String> builder().add("key", "value").toMap();
167         final CopyOnWriteMap<String, String> cowMap = CopyOnWriteMap.<String, String> builder().addAll(map).newLinkedMap();
168         assertEquals(map.entrySet(), cowMap.entrySet());
169         assertEquals(map.entrySet().hashCode(), cowMap.entrySet().hashCode());
170         assertEquals(map.entrySet().toString(), cowMap.entrySet().toString());
171     }
172 
173     @Test
174     public void putIfAbsentWorksIfAbsent() throws Exception {
175         final Map<String, String> init = MapBuilder.<String, String> builder().add("key", "value").toMap();
176         final ConcurrentMap<String, String> map = CopyOnWriteMap.<String, String> builder().addAll(init).newHashMap();
177         assertNull(map.putIfAbsent("key2", "value2"));
178         assertEquals(2, map.size());
179         assertTrue(map.containsKey("key2"));
180         assertTrue(map.containsValue("value2"));
181     }
182 
183     @Test
184     public void putIfAbsentFailsIfPresent() throws Exception {
185         final Map<String, String> init = MapBuilder.<String, String> builder().add("key", "value").add("key2", "value2").toMap();
186         final ConcurrentMap<String, String> map = CopyOnWriteMap.<String, String> builder().addAll(init).newHashMap();
187         map.putIfAbsent("key2", "value3");
188         assertNotNull(map.putIfAbsent("key2", "value2"));
189         assertEquals(2, map.size());
190         assertTrue(map.containsKey("key2"));
191         assertTrue(map.containsValue("value2"));
192         assertFalse(map.containsValue("value3"));
193     }
194 
195     @Test
196     public void removeWorksIfValueSame() throws Exception {
197         final Map<String, String> init = MapBuilder.<String, String> builder().add("key", "value").add("key2", "value2").toMap();
198         final ConcurrentMap<String, String> map = CopyOnWriteMap.<String, String> builder().addAll(init).newHashMap();
199         assertTrue(map.remove("key2", "value2"));
200         assertEquals(1, map.size());
201         assertFalse(map.containsKey("key2"));
202         assertFalse(map.containsValue("value2"));
203     }
204 
205     @Test
206     public void removeFailsIfValueDifferent() throws Exception {
207         final Map<String, String> init = MapBuilder.<String, String> builder().add("key", "value").add("key2", "value2").toMap();
208         final ConcurrentMap<String, String> map = CopyOnWriteMap.<String, String> builder().addAll(init).newHashMap();
209         assertFalse(map.remove("key2", "value3"));
210         assertEquals(2, map.size());
211         assertTrue(map.containsKey("key2"));
212         assertTrue(map.containsValue("value2"));
213         assertFalse(map.containsValue("value3"));
214     }
215 
216     @Test
217     public void unconditionalReplaceWorksIfKeyMapped() throws Exception {
218         final Map<String, String> init = MapBuilder.<String, String> builder().add("key", "value").add("key2", "value2").toMap();
219         final ConcurrentMap<String, String> map = CopyOnWriteMap.<String, String> builder().addAll(init).newHashMap();
220         assertNotNull(map.replace("key2", "value3"));
221         assertEquals(2, map.size());
222         assertTrue(map.containsKey("key2"));
223         assertTrue(map.containsValue("value3"));
224         assertFalse(map.containsValue("value2"));
225     }
226 
227     @Test
228     public void unconditionalReplaceFailsIfKeyMissing() throws Exception {
229         final Map<String, String> init = MapBuilder.<String, String> builder().add("key", "value").add("key2", "value2").toMap();
230         final ConcurrentMap<String, String> map = CopyOnWriteMap.<String, String> builder().addAll(init).newHashMap();
231         assertNull(map.replace("key3", "value3"));
232         assertEquals(2, map.size());
233         assertTrue(map.containsKey("key2"));
234         assertTrue(map.containsValue("value2"));
235         assertFalse(map.containsKey("key3"));
236         assertFalse(map.containsValue("value3"));
237     }
238 
239     @Test
240     public void conditionalReplaceWorksIfKeyAndValueMapped() throws Exception {
241         final Map<String, String> init = MapBuilder.<String, String> builder().add("key", "value").add("key2", "value2").toMap();
242         final ConcurrentMap<String, String> map = CopyOnWriteMap.<String, String> builder().addAll(init).newHashMap();
243         assertTrue(map.replace("key2", "value2", "value3"));
244         assertEquals(2, map.size());
245         assertTrue(map.containsKey("key2"));
246         assertTrue(map.containsValue("value3"));
247         assertFalse(map.containsValue("value2"));
248     }
249 
250     @Test
251     public void conditionalReplaceWorksIfValueNull() throws Exception {
252         final Map<String, String> init = MapBuilder.<String, String> builder().add("key", "value").add("key2", null).toMap();
253         final ConcurrentMap<String, String> map = CopyOnWriteMap.<String, String> builder().addAll(init).newHashMap();
254         assertTrue(map.replace("key2", null, "value3"));
255         assertEquals(2, map.size());
256         assertTrue(map.containsKey("key2"));
257         assertTrue(map.containsValue("value3"));
258         assertFalse(map.containsValue(null));
259     }
260 
261     @Test
262     public void conditionalReplaceFailsIfValueDifferent() throws Exception {
263         final Map<String, String> init = MapBuilder.<String, String> builder().add("key", "value").add("key2", "value2").toMap();
264         final ConcurrentMap<String, String> map = CopyOnWriteMap.<String, String> builder().addAll(init).newHashMap();
265         assertFalse(map.replace("key2", "value3", "value4"));
266         assertEquals(2, map.size());
267         assertTrue(map.containsKey("key2"));
268         assertTrue(map.containsValue("value2"));
269         assertFalse(map.containsValue("value3"));
270         assertFalse(map.containsValue("value4"));
271     }
272 
273     @Test
274     public void conditionalReplaceFailsIfKeyMissing() throws Exception {
275         final Map<String, String> init = MapBuilder.<String, String> builder().add("key", "value").add("key2", "value2").toMap();
276         final ConcurrentMap<String, String> map = CopyOnWriteMap.<String, String> builder().addAll(init).newHashMap();
277         assertFalse(map.replace("key3", "value2", "value3"));
278         assertEquals(2, map.size());
279         assertTrue(map.containsKey("key2"));
280         assertTrue(map.containsValue("value2"));
281         assertFalse(map.containsKey("key3"));
282         assertFalse(map.containsValue("value3"));
283     }
284 
285     @Test
286     public void modifiableValues() throws Exception {
287         final AtomicInteger count = new AtomicInteger();
288         final Map<String, String> init = new MapBuilder<String, String>().add("test", "test").add("testing", "testing").add("sup", "tester").toMap();
289         final Map<String, String> map = new CopyOnWriteMap<String, String>(init, View.Type.LIVE) {
290             private static final long serialVersionUID = 3275978982528321604L;
291 
292             @Override
293             public <N extends Map<? extends String, ? extends String>> Map<String, String> copy(final N map) {
294                 count.getAndIncrement();
295                 return new HashMap<String, String>(map);
296             }
297         };
298         assertEquals(1, count.get());
299         final Collection<String> values = map.values();
300         try {
301             values.add("something");
302             fail("UnsupportedOp expected");
303         } catch (final UnsupportedOperationException ignore) {}
304         assertEquals(1, count.get());
305         try {
306             values.addAll(asList("one", "two", "three"));
307             fail("UnsupportedOp expected");
308         } catch (final UnsupportedOperationException ignore) {}
309         final Iterator<String> iterator = values.iterator();
310         assertTrue(iterator.hasNext());
311         assertNotNull(iterator.next());
312         try {
313             iterator.remove();
314             fail("UnsupportedOp expected");
315         } catch (final UnsupportedOperationException ignore) {}
316         assertEquals(1, count.get());
317         assertFalse(values.remove("blah"));
318         assertEquals("not modified if element not present to be removed", 1, count.get());
319         assertTrue(values.remove("test"));
320         assertEquals(2, count.get());
321         assertEquals(2, map.size());
322         assertFalse(values.retainAll(asList("testing", "tester")));
323         assertEquals(3, count.get());
324         assertEquals(2, map.size());
325         assertTrue(values.removeAll(asList("test", "testing")));
326         assertEquals(4, count.get());
327         assertEquals(1, map.size());
328         values.clear();
329         assertTrue(map.isEmpty());
330     }
331 
332     @Test
333     public void modifiableEntrySet() throws Exception {
334         final AtomicInteger count = new AtomicInteger();
335         final Map<String, String> init = new MapBuilder<String, String>().add("test", "test").add("testing", "testing").add("tester", "tester")
336             .toMap();
337         final Map<String, String> map = new CopyOnWriteMap<String, String>(init, View.Type.LIVE) {
338             private static final long serialVersionUID = -2882860445706454721L;
339 
340             @Override
341             public <N extends Map<? extends String, ? extends String>> Map<String, String> copy(final N map) {
342                 count.getAndIncrement();
343                 return new HashMap<String, String>(map);
344             }
345         };
346         assertEquals(1, count.get());
347         final Collection<Entry<String, String>> entries = map.entrySet();
348 
349         try {
350             entries.add(new E("something"));
351             fail("UnsupportedOp expected");
352         } catch (final UnsupportedOperationException ignore) {}
353         assertEquals(1, count.get());
354         try {
355             entries.addAll(asList(new E("one"), new E("two"), new E("three")));
356             fail("UnsupportedOp expected");
357         } catch (final UnsupportedOperationException ignore) {}
358         final Iterator<Entry<String, String>> iterator = entries.iterator();
359         assertTrue(iterator.hasNext());
360         assertNotNull(iterator.next());
361         try {
362             iterator.remove();
363             fail("UnsupportedOp expected");
364         } catch (final UnsupportedOperationException ignore) {}
365         assertEquals(1, count.get());
366         assertFalse(entries.remove("blah"));
367         assertEquals("not modified if element not present to be removed", 1, count.get());
368         assertTrue(entries.remove(new E("test")));
369         assertEquals(2, count.get());
370         assertEquals(2, map.size());
371         assertFalse(entries.retainAll(asList(new E("testing"), new E("tester"))));
372         assertEquals(3, count.get());
373         assertEquals(2, map.size());
374         assertTrue(entries.removeAll(asList(new E("test"), new E("testing"))));
375         assertEquals(4, count.get());
376         assertEquals(1, map.size());
377         entries.clear();
378         assertTrue(map.isEmpty());
379         map.put("key", "key");
380         assertTrue(entries.contains(new E("key")));
381     }
382 
383     @Test
384     public void modifiableKeySet() throws Exception {
385         final AtomicInteger count = new AtomicInteger();
386 
387         final Map<String, String> init = new MapBuilder<String, String>().add("test", "test").add("testing", "testing").add("tester", "tester")
388             .toMap();
389         final Map<String, String> map = new CopyOnWriteMap<String, String>(init, View.Type.LIVE) {
390             private static final long serialVersionUID = 7273654247572679525L;
391 
392             @Override
393             public <N extends Map<? extends String, ? extends String>> Map<String, String> copy(final N map) {
394                 count.getAndIncrement();
395                 return new HashMap<String, String>(map);
396             }
397         };
398         assertEquals(1, count.get());
399         final Collection<String> keys = map.keySet();
400         try {
401             keys.add("something");
402             fail("UnsupportedOp expected");
403         } catch (final UnsupportedOperationException ignore) {}
404         assertEquals(1, count.get());
405         try {
406             keys.addAll(asList("one", "two", "three"));
407             fail("UnsupportedOp expected");
408         } catch (final UnsupportedOperationException ignore) {}
409         final Iterator<String> iterator = keys.iterator();
410         assertTrue(iterator.hasNext());
411         assertNotNull(iterator.next());
412         try {
413             iterator.remove();
414             fail("UnsupportedOp expected");
415         } catch (final UnsupportedOperationException ignore) {}
416         assertEquals(1, count.get());
417         assertFalse(keys.remove("blah"));
418         assertEquals("not modified if element not present to be removed", 1, count.get());
419         assertTrue(keys.remove("test"));
420         assertEquals(2, count.get());
421         assertEquals(2, map.size());
422         assertFalse(keys.retainAll(asList("testing", "tester")));
423         assertEquals(3, count.get());
424         assertEquals(2, map.size());
425         assertTrue(keys.removeAll(asList("test", "testing")));
426         assertEquals(4, count.get());
427         assertEquals(1, map.size());
428         keys.clear();
429         assertTrue(map.isEmpty());
430         map.put("key", "key");
431         assertTrue(keys.contains("key"));
432     }
433 
434     @Test
435     public void unmodifiableValues() throws Exception {
436         final AtomicInteger count = new AtomicInteger();
437         final Map<String, String> init = new MapBuilder<String, String>().add("test", "test").add("testing", "testing").add("sup", "tester").toMap();
438         final Map<String, String> map = new CopyOnWriteMap<String, String>(init, View.Type.STABLE) {
439             private static final long serialVersionUID = 3275978982528321604L;
440 
441             @Override
442             public <N extends Map<? extends String, ? extends String>> Map<String, String> copy(final N map) {
443                 count.getAndIncrement();
444                 return new HashMap<String, String>(map);
445             }
446         };
447         assertEquals(1, count.get());
448         final Collection<String> values = map.values();
449         try {
450             values.add("something");
451             fail("UnsupportedOp expected");
452         } catch (final UnsupportedOperationException ignore) {}
453         assertEquals(1, count.get());
454         try {
455             values.addAll(asList("one", "two", "three"));
456             fail("UnsupportedOp expected");
457         } catch (final UnsupportedOperationException ignore) {}
458         final Iterator<String> iterator = values.iterator();
459         assertTrue(iterator.hasNext());
460         assertNotNull(iterator.next());
461         try {
462             iterator.remove();
463             fail("UnsupportedOp expected");
464         } catch (final UnsupportedOperationException ignore) {}
465         assertEquals(1, count.get());
466         try {
467             values.remove("blah");
468             fail("UnsupportedOp expected");
469         } catch (final UnsupportedOperationException ignore) {}
470         try {
471             values.retainAll(asList("testing", "tester"));
472             fail("UnsupportedOp expected");
473         } catch (final UnsupportedOperationException ignore) {}
474         try {
475             values.removeAll(asList("test", "testing"));
476             fail("UnsupportedOp expected");
477         } catch (final UnsupportedOperationException ignore) {}
478         try {
479             values.clear();
480             fail("UnsupportedOp expected");
481         } catch (final UnsupportedOperationException ignore) {}
482         map.put("lalala", "lalala");
483         assertFalse(values.contains("lalala"));
484         CopyOnWriteSortedMapTest.assertUnmodifiableCollection(values, "lalala");
485     }
486 
487     @Test
488     public void unmodifiableEntrySet() throws Exception {
489         final AtomicInteger count = new AtomicInteger();
490         final Map<String, String> init = new MapBuilder<String, String>().add("test", "test").add("testing", "testing").add("tester", "tester")
491             .toMap();
492         final Map<String, String> map = new CopyOnWriteMap<String, String>(init, View.Type.STABLE) {
493             private static final long serialVersionUID = -2882860445706454721L;
494 
495             @Override
496             public <N extends Map<? extends String, ? extends String>> Map<String, String> copy(final N map) {
497                 count.getAndIncrement();
498                 return new HashMap<String, String>(map);
499             }
500         };
501         assertEquals(1, count.get());
502         final Collection<Entry<String, String>> entries = map.entrySet();
503 
504         try {
505             entries.add(new E("something"));
506             fail("UnsupportedOp expected");
507         } catch (final UnsupportedOperationException ignore) {}
508         assertEquals(1, count.get());
509         try {
510             entries.addAll(asList(new E("one"), new E("two"), new E("three")));
511             fail("UnsupportedOp expected");
512         } catch (final UnsupportedOperationException ignore) {}
513         final Iterator<Entry<String, String>> iterator = entries.iterator();
514         assertTrue(iterator.hasNext());
515         assertNotNull(iterator.next());
516         try {
517             iterator.remove();
518             fail("UnsupportedOp expected");
519         } catch (final UnsupportedOperationException ignore) {}
520         assertEquals(1, count.get());
521         try {
522             assertFalse(entries.remove("blah"));
523             fail("UnsupportedOp expected");
524         } catch (final UnsupportedOperationException ignore) {}
525         try {
526             entries.retainAll(asList(new E("testing"), new E("tester")));
527             fail("UnsupportedOp expected");
528         } catch (final UnsupportedOperationException ignore) {}
529         try {
530             entries.removeAll(asList(new E("test"), new E("testing")));
531             fail("UnsupportedOp expected");
532         } catch (final UnsupportedOperationException ignore) {}
533         try {
534             entries.clear();
535             fail("UnsupportedOp expected");
536         } catch (final UnsupportedOperationException ignore) {}
537         map.put("key", "key");
538         assertFalse(entries.contains(new E("key")));
539         CopyOnWriteSortedMapTest.assertUnmodifiableCollection(entries, new E("lalala"));
540     }
541 
542     @Test
543     public void unmodifiableKeySet() throws Exception {
544         final AtomicInteger count = new AtomicInteger();
545 
546         final Map<String, String> init = new MapBuilder<String, String>().add("test", "test").add("testing", "testing").add("tester", "tester")
547             .toMap();
548         final Map<String, String> map = new CopyOnWriteMap<String, String>(init, View.Type.STABLE) {
549             private static final long serialVersionUID = 7273654247572679525L;
550 
551             @Override
552             public <N extends Map<? extends String, ? extends String>> Map<String, String> copy(final N map) {
553                 count.getAndIncrement();
554                 return new HashMap<String, String>(map);
555             }
556         };
557         assertEquals(1, count.get());
558         final Collection<String> keys = map.keySet();
559         try {
560             keys.add("something");
561             fail("UnsupportedOp expected");
562         } catch (final UnsupportedOperationException ignore) {}
563         assertEquals(1, count.get());
564         try {
565             keys.addAll(asList("one", "two", "three"));
566             fail("UnsupportedOp expected");
567         } catch (final UnsupportedOperationException ignore) {}
568         final Iterator<String> iterator = keys.iterator();
569         assertTrue(iterator.hasNext());
570         assertNotNull(iterator.next());
571         try {
572             iterator.remove();
573             fail("UnsupportedOp expected");
574         } catch (final UnsupportedOperationException ignore) {}
575         assertEquals(1, count.get());
576         try {
577             keys.remove("blah");
578             fail("UnsupportedOp expected");
579         } catch (final UnsupportedOperationException ignore) {}
580         try {
581             keys.remove("test");
582             fail("UnsupportedOp expected");
583         } catch (final UnsupportedOperationException ignore) {}
584         try {
585             keys.retainAll(asList("testing", "tester"));
586             fail("UnsupportedOp expected");
587         } catch (final UnsupportedOperationException ignore) {}
588         try {
589             keys.removeAll(asList("test", "testing"));
590             fail("UnsupportedOp expected");
591         } catch (final UnsupportedOperationException ignore) {}
592         try {
593             keys.clear();
594             fail("UnsupportedOp expected");
595         } catch (final UnsupportedOperationException ignore) {}
596         map.put("key", "lala");
597         assertFalse(keys.contains("key"));
598         CopyOnWriteSortedMapTest.assertUnmodifiableCollection(keys, "key");
599     }
600 
601     @Test(expected = IllegalArgumentException.class)
602     public void nullMap() throws Exception {
603         new CopyOnWriteMap<String, String>(null, View.Type.STABLE) {
604             private static final long serialVersionUID = 4223850632932526917L;
605 
606             // /CLOVER:OFF
607             @Override
608             public <N extends Map<? extends String, ? extends String>> Map<String, String> copy(final N map) {
609                 return new HashMap<String, String>(map);
610             };
611             // /CLOVER:ON
612         };
613     }
614 
615     @Test(expected = IllegalArgumentException.class)
616     public void nullViewType() throws Exception {
617         new CopyOnWriteMap<String, String>(new HashMap<String, String>(), null) {
618             private static final long serialVersionUID = 4223850632932526917L;
619 
620             // /CLOVER:OFF
621             @Override
622             public <N extends Map<? extends String, ? extends String>> Map<String, String> copy(final N map) {
623                 return new HashMap<String, String>(map);
624             };
625             // /CLOVER:ON
626         };
627     }
628 
629     @Test(expected = IllegalArgumentException.class)
630     public void copyFunctionReturnsNull() throws Exception {
631         new CopyOnWriteMap<String, String>(View.Type.STABLE) {
632             private static final long serialVersionUID = 831716474176011289L;
633 
634             @Override
635             public <N extends Map<? extends String, ? extends String>> Map<String, String> copy(final N map) {
636                 return null;
637             };
638         };
639     }
640 
641     @Test
642     public void serializableHashMap() {
643         assertMutableMapSerializable(CopyOnWriteMap.builder().newHashMap());
644     }
645 
646     @Test
647     public void serializableLinkedMap() {
648         assertMutableMapSerializable(CopyOnWriteMap.builder().newLinkedMap());
649     }
650 
651     @Test
652     public void toStringTest() throws Exception {
653         final AtomicReference<Map<String, String>> ref = new AtomicReference<Map<String, String>>();
654         final CopyOnWriteMap<String, String> cowMap = new CopyOnWriteMap<String, String>(View.Type.STABLE) {
655             private static final long serialVersionUID = -17380087385174856L;
656 
657             @Override
658             protected <N extends Map<? extends String, ? extends String>> java.util.Map<String, String> copy(final N map) {
659                 ref.set(new HashMap<String, String>(map));
660                 return ref.get();
661             };
662         };
663         assertEquals(ref.get().toString(), cowMap.toString());
664     }
665 
666     @Test
667     public void isEmpty() throws Exception {
668         final CopyOnWriteMap<String, String> cowMap = CopyOnWriteMap.<String, String> builder().newHashMap();
669         assertTrue(cowMap.isEmpty());
670         assertTrue(cowMap.keySet().isEmpty());
671         assertTrue(cowMap.entrySet().isEmpty());
672         assertTrue(cowMap.values().isEmpty());
673         cowMap.put("1", "1");
674         assertFalse(cowMap.isEmpty());
675         assertFalse(cowMap.keySet().isEmpty());
676         assertFalse(cowMap.entrySet().isEmpty());
677         assertFalse(cowMap.values().isEmpty());
678     }
679 
680     @Test
681     public void equality() throws Exception {
682         final Map<String, String> init = new MapBuilder<String, String>().add("test", "test").add("testing", "testing").toMap();
683         final CopyOnWriteMap<String, String> map = CopyOnWriteMap.<String, String> builder().addAll(init).newHashMap();
684         assertEquals(init, map);
685         assertEquals(map, init);
686         assertEquals(init.hashCode(), map.hashCode());
687         assertEquals(map.hashCode(), init.hashCode());
688         assertEquals(init.keySet(), map.keySet());
689         assertEquals(map.keySet(), init.keySet());
690         assertEquals(init.entrySet(), map.entrySet());
691         assertEquals(map.entrySet(), init.entrySet());
692         assertFalse(init.values().equals(map.values()));
693         assertFalse(map.values().equals(init.values()));
694     }
695 
696     @Test
697     public void toArray() throws Exception {
698         final Map<String, String> init = new MapBuilder<String, String>().add("test", "test").add("testing", "testing").toMap();
699         final CopyOnWriteMap<String, String> map = CopyOnWriteMap.<String, String> builder().addAll(init).newHashMap();
700         assertArrayEquals(init.keySet().toArray(new String[2]), map.keySet().toArray(new String[2]));
701         assertArrayEquals(init.values().toArray(new String[2]), map.values().toArray(new String[2]));
702         assertArrayEquals(init.entrySet().toArray(new Map.Entry[2]), map.entrySet().toArray(new Map.Entry[2]));
703     }
704 
705     @Test
706     public void contains() throws Exception {
707         final Map<String, String> init = MapBuilder.build("1", "o1", "2", "o2", "3", "o3");
708         final Map<String, String> map = CopyOnWriteMap.<String, String> builder().addAll(init).newHashMap();
709         assertTrue(map.containsKey("2"));
710         assertTrue(map.containsValue("o2"));
711         assertTrue(map.keySet().contains("2"));
712         assertTrue(map.keySet().containsAll(asList(new String[] { "1", "2", "3" })));
713         assertTrue(map.values().contains("o2"));
714         assertTrue(map.values().containsAll(asList(new String[] { "o1", "o2", "o3" })));
715     }
716 
717     static void assertMutableMapSerializable(Map<Object, Object> map) {
718         map.put("1", "one");
719         map = assertSerializable(map);
720         assertTrue(map.containsKey("1"));
721         assertTrue(map.containsValue("one"));
722         assertEquals("1", map.keySet().iterator().next());
723         assertEquals("one", map.values().iterator().next());
724         final Map.Entry<Object, Object> entry = map.entrySet().iterator().next();
725         assertEquals("1", entry.getKey());
726         assertEquals("one", entry.getValue());
727     }
728 
729     static Map<Object, Object> assertSerializable(final Map<Object, Object> map) {
730         final Map<Object, Object> result = serialize(map);
731         assertEquals(map, result);
732         return result;
733     }
734 }
735 
736 class MapBuilder<K, V> {
737     private final Map<K, V> map = new HashMap<K, V>();
738 
739     static <S> Map<S, S> build(final S... elements) {
740         if (elements.length % 2 != 0) {
741             throw new IllegalArgumentException("must have even number of elements: " + elements.length);
742         }
743         final MapBuilder<S, S> result = new MapBuilder<S, S>();
744         for (int i = 0; i < elements.length; i = i + 2) {
745             result.add(elements[i], elements[i + 1]);
746         }
747         return result.toMap();
748     }
749 
750     static <K, V> MapBuilder<K, V> builder() {
751         return new MapBuilder<K, V>();
752     }
753 
754     MapBuilder<K, V> add(final K key, final V value) {
755         map.put(key, value);
756         return this;
757     }
758 
759     Entry<K, V> entry(final K key, final V value) {
760         return new Entry<K, V>() {
761             public K getKey() {
762                 return key;
763             }
764 
765             public V getValue() {
766                 return value;
767             }
768 
769             public V setValue(final V arg0) {
770                 throw new UnsupportedOperationException();
771             };
772         };
773     }
774 
775     Map<K, V> toMap() {
776         return Collections.unmodifiableMap(new HashMap<K, V>(map));
777     }
778 
779     static class E implements Map.Entry<String, String> {
780         final String e;
781 
782         public E(final String e) {
783             this.e = e;
784         }
785 
786         public String getKey() {
787             return e;
788         }
789 
790         public String getValue() {
791             return e;
792         }
793 
794         public String setValue(final String value) {
795             throw new RuntimeException("should not be called, don't use UnsupportedOp here");
796         }
797     }
798 }