View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.beanutils;
18  
19  import java.util.Collection;
20  import java.util.ConcurrentModificationException;
21  import java.util.HashMap;
22  import java.util.Iterator;
23  import java.util.Map;
24  import java.util.Set;
25  import java.util.WeakHashMap;
26  
27  /**
28   * <p>A customized implementation of <code>java.util.HashMap</code> designed
29   * to operate in a multithreaded environment where the large majority of
30   * method calls are read-only, instead of structural changes.  When operating
31   * in "fast" mode, read calls are non-synchronized and write calls perform the
32   * following steps:</p>
33   * <ul>
34   * <li>Clone the existing collection
35   * <li>Perform the modification on the clone
36   * <li>Replace the existing collection with the (modified) clone
37   * </ul>
38   * <p>When first created, objects of this class default to "slow" mode, where
39   * all accesses of any type are synchronized but no cloning takes place.  This
40   * is appropriate for initially populating the collection, followed by a switch
41   * to "fast" mode (by calling <code>setFast(true)</code>) after initialization
42   * is complete.</p>
43   *
44   * <p><strong>NOTE</strong>: If you are creating and accessing a
45   * <code>HashMap</code> only within a single thread, you should use
46   * <code>java.util.HashMap</code> directly (with no synchronization), for
47   * maximum performance.</p>
48   *
49   * <p><strong>NOTE</strong>: <i>This class is not cross-platform.
50   * Using it may cause unexpected failures on some architectures.</i>
51   * It suffers from the same problems as the double-checked locking idiom.
52   * In particular, the instruction that clones the internal collection and the
53   * instruction that sets the internal reference to the clone can be executed
54   * or perceived out-of-order.  This means that any read operation might fail
55   * unexpectedly, as it may be reading the state of the internal collection
56   * before the internal collection is fully formed.
57   * For more information on the double-checked locking idiom, see the
58   * <a href="http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html">
59   * Double-Checked Locking Idiom Is Broken Declaration</a>.</p>
60   *
61   * @since Commons Collections 1.0
62   * @version $Id$
63   */
64  class WeakFastHashMap<K, V> extends HashMap<K, V> {
65  
66      /**
67       * The underlying map we are managing.
68       */
69      private Map<K, V> map = null;
70  
71      /**
72       * Are we currently operating in "fast" mode?
73       */
74      private boolean fast = false;
75  
76      // Constructors
77      // ----------------------------------------------------------------------
78  
79      /**
80       * Construct an empty map.
81       */
82      public WeakFastHashMap() {
83          super();
84          this.map = createMap();
85      }
86  
87      /**
88       * Construct an empty map with the specified capacity.
89       *
90       * @param capacity  the initial capacity of the empty map
91       */
92      public WeakFastHashMap(final int capacity) {
93          super();
94          this.map = createMap(capacity);
95      }
96  
97      /**
98       * Construct an empty map with the specified capacity and load factor.
99       *
100      * @param capacity  the initial capacity of the empty map
101      * @param factor  the load factor of the new map
102      */
103     public WeakFastHashMap(final int capacity, final float factor) {
104         super();
105         this.map = createMap(capacity, factor);
106     }
107 
108     /**
109      * Construct a new map with the same mappings as the specified map.
110      *
111      * @param map  the map whose mappings are to be copied
112      */
113     public WeakFastHashMap(final Map<? extends K, ? extends V> map) {
114         super();
115         this.map = createMap(map);
116     }
117 
118 
119     // Property access
120     // ----------------------------------------------------------------------
121 
122     /**
123      *  Returns true if this map is operating in fast mode.
124      *
125      *  @return true if this map is operating in fast mode
126      */
127     public boolean getFast() {
128         return (this.fast);
129     }
130 
131     /**
132      *  Sets whether this map is operating in fast mode.
133      *
134      *  @param fast true if this map should operate in fast mode
135      */
136     public void setFast(final boolean fast) {
137         this.fast = fast;
138     }
139 
140 
141     // Map access
142     // ----------------------------------------------------------------------
143     // These methods can forward straight to the wrapped Map in 'fast' mode.
144     // (because they are query methods)
145 
146     /**
147      * Return the value to which this map maps the specified key.  Returns
148      * <code>null</code> if the map contains no mapping for this key, or if
149      * there is a mapping with a value of <code>null</code>.  Use the
150      * <code>containsKey()</code> method to disambiguate these cases.
151      *
152      * @param key  the key whose value is to be returned
153      * @return the value mapped to that key, or null
154      */
155     @Override
156     public V get(final Object key) {
157         if (fast) {
158             return (map.get(key));
159         } else {
160             synchronized (map) {
161                 return (map.get(key));
162             }
163         }
164     }
165 
166     /**
167      * Return the number of key-value mappings in this map.
168      *
169      * @return the current size of the map
170      */
171     @Override
172     public int size() {
173         if (fast) {
174             return (map.size());
175         } else {
176             synchronized (map) {
177                 return (map.size());
178             }
179         }
180     }
181 
182     /**
183      * Return <code>true</code> if this map contains no mappings.
184      *
185      * @return is the map currently empty
186      */
187     @Override
188     public boolean isEmpty() {
189         if (fast) {
190             return (map.isEmpty());
191         } else {
192             synchronized (map) {
193                 return (map.isEmpty());
194             }
195         }
196     }
197 
198     /**
199      * Return <code>true</code> if this map contains a mapping for the
200      * specified key.
201      *
202      * @param key  the key to be searched for
203      * @return true if the map contains the key
204      */
205     @Override
206     public boolean containsKey(final Object key) {
207         if (fast) {
208             return (map.containsKey(key));
209         } else {
210             synchronized (map) {
211                 return (map.containsKey(key));
212             }
213         }
214     }
215 
216     /**
217      * Return <code>true</code> if this map contains one or more keys mapping
218      * to the specified value.
219      *
220      * @param value  the value to be searched for
221      * @return true if the map contains the value
222      */
223     @Override
224     public boolean containsValue(final Object value) {
225         if (fast) {
226             return (map.containsValue(value));
227         } else {
228             synchronized (map) {
229                 return (map.containsValue(value));
230             }
231         }
232     }
233 
234     // Map modification
235     // ----------------------------------------------------------------------
236     // These methods perform special behaviour in 'fast' mode.
237     // The map is cloned, updated and then assigned back.
238     // See the comments at the top as to why this won't always work.
239 
240     /**
241      * Associate the specified value with the specified key in this map.
242      * If the map previously contained a mapping for this key, the old
243      * value is replaced and returned.
244      *
245      * @param key  the key with which the value is to be associated
246      * @param value  the value to be associated with this key
247      * @return the value previously mapped to the key, or null
248      */
249     @Override
250     public V put(final K key, final V value) {
251         if (fast) {
252             synchronized (this) {
253                 final Map<K, V> temp = cloneMap(map);
254                 final V result = temp.put(key, value);
255                 map = temp;
256                 return (result);
257             }
258         } else {
259             synchronized (map) {
260                 return (map.put(key, value));
261             }
262         }
263     }
264 
265     /**
266      * Copy all of the mappings from the specified map to this one, replacing
267      * any mappings with the same keys.
268      *
269      * @param in  the map whose mappings are to be copied
270      */
271     @Override
272     public void putAll(final Map<? extends K, ? extends V> in) {
273         if (fast) {
274             synchronized (this) {
275                 final Map<K, V> temp =  cloneMap(map);
276                 temp.putAll(in);
277                 map = temp;
278             }
279         } else {
280             synchronized (map) {
281                 map.putAll(in);
282             }
283         }
284     }
285 
286     /**
287      * Remove any mapping for this key, and return any previously
288      * mapped value.
289      *
290      * @param key  the key whose mapping is to be removed
291      * @return the value removed, or null
292      */
293     @Override
294     public V remove(final Object key) {
295         if (fast) {
296             synchronized (this) {
297                 final Map<K, V> temp = cloneMap(map);
298                 final V result = temp.remove(key);
299                 map = temp;
300                 return (result);
301             }
302         } else {
303             synchronized (map) {
304                 return (map.remove(key));
305             }
306         }
307     }
308 
309     /**
310      * Remove all mappings from this map.
311      */
312     @Override
313     public void clear() {
314         if (fast) {
315             synchronized (this) {
316                 map = createMap();
317             }
318         } else {
319             synchronized (map) {
320                 map.clear();
321             }
322         }
323     }
324 
325     // Basic object methods
326     // ----------------------------------------------------------------------
327 
328     /**
329      * Compare the specified object with this list for equality.  This
330      * implementation uses exactly the code that is used to define the
331      * list equals function in the documentation for the
332      * <code>Map.equals</code> method.
333      *
334      * @param o  the object to be compared to this list
335      * @return true if the two maps are equal
336      */
337     @Override
338     public boolean equals(final Object o) {
339         // Simple tests that require no synchronization
340         if (o == this) {
341             return (true);
342         } else if (!(o instanceof Map)) {
343             return (false);
344         }
345         final Map<?, ?> mo = (Map<?, ?>) o;
346 
347         // Compare the two maps for equality
348         if (fast) {
349             if (mo.size() != map.size()) {
350                 return (false);
351             }
352             for (final Map.Entry<K, V> e : map.entrySet()) {
353                 final K key = e.getKey();
354                 final V value = e.getValue();
355                 if (value == null) {
356                     if (!(mo.get(key) == null && mo.containsKey(key))) {
357                         return (false);
358                     }
359                 } else {
360                     if (!value.equals(mo.get(key))) {
361                         return (false);
362                     }
363                 }
364             }
365             return (true);
366 
367         } else {
368             synchronized (map) {
369                 if (mo.size() != map.size()) {
370                     return (false);
371                 }
372                 for (final Map.Entry<K, V> e : map.entrySet()) {
373                     final K key = e.getKey();
374                     final V value = e.getValue();
375                     if (value == null) {
376                         if (!(mo.get(key) == null && mo.containsKey(key))) {
377                             return (false);
378                         }
379                     } else {
380                         if (!value.equals(mo.get(key))) {
381                             return (false);
382                         }
383                     }
384                 }
385                 return (true);
386             }
387         }
388     }
389 
390     /**
391      * Return the hash code value for this map.  This implementation uses
392      * exactly the code that is used to define the list hash function in the
393      * documentation for the <code>Map.hashCode</code> method.
394      *
395      * @return suitable integer hash code
396      */
397     @Override
398     public int hashCode() {
399         if (fast) {
400             int h = 0;
401             for (final Map.Entry<K, V> e : map.entrySet()) {
402                 h += e.hashCode();
403             }
404             return (h);
405         } else {
406             synchronized (map) {
407                 int h = 0;
408                 for (final Map.Entry<K, V> e : map.entrySet()) {
409                     h += e.hashCode();
410                 }
411                 return (h);
412             }
413         }
414     }
415 
416     /**
417      * Return a shallow copy of this <code>FastHashMap</code> instance.
418      * The keys and values themselves are not copied.
419      *
420      * @return a clone of this map
421      */
422     @Override
423     public Object clone() {
424         WeakFastHashMap<K, V> results = null;
425         if (fast) {
426             results = new WeakFastHashMap<K, V>(map);
427         } else {
428             synchronized (map) {
429                 results = new WeakFastHashMap<K, V>(map);
430             }
431         }
432         results.setFast(getFast());
433         return (results);
434     }
435 
436     // Map views
437     // ----------------------------------------------------------------------
438 
439     /**
440      * Return a collection view of the mappings contained in this map.  Each
441      * element in the returned collection is a <code>Map.Entry</code>.
442      * @return the set of map Map entries
443      */
444     @Override
445     public Set<Map.Entry<K, V>> entrySet() {
446         return new EntrySet();
447     }
448 
449     /**
450      * Return a set view of the keys contained in this map.
451      * @return the set of the Map's keys
452      */
453     @Override
454     public Set<K> keySet() {
455         return new KeySet();
456     }
457 
458     /**
459      * Return a collection view of the values contained in this map.
460      * @return the set of the Map's values
461      */
462     @Override
463     public Collection<V> values() {
464         return new Values();
465     }
466 
467     // Abstractions on Map creations (for subclasses such as WeakFastHashMap)
468     // ----------------------------------------------------------------------
469 
470     protected Map<K, V> createMap() {
471         return new WeakHashMap<K, V>();
472     }
473 
474     protected Map<K, V> createMap(final int capacity) {
475         return new WeakHashMap<K, V>(capacity);
476     }
477 
478     protected Map<K, V> createMap(final int capacity, final float factor) {
479         return new WeakHashMap<K, V>(capacity, factor);
480     }
481 
482     protected Map<K, V> createMap(final Map<? extends K, ? extends V> map) {
483         return new WeakHashMap<K, V>(map);
484     }
485 
486     protected Map<K, V> cloneMap(final Map<? extends K, ? extends V> map) {
487         return createMap(map);
488     }
489 
490     // Map view inner classes
491     // ----------------------------------------------------------------------
492 
493     /**
494      * Abstract collection implementation shared by keySet(), values() and entrySet().
495      *
496      * @param <E> the element type
497      */
498     private abstract class CollectionView<E> implements Collection<E> {
499 
500         public CollectionView() {
501         }
502 
503         protected abstract Collection<E> get(Map<K, V> map);
504         protected abstract E iteratorNext(Map.Entry<K, V> entry);
505 
506 
507         public void clear() {
508             if (fast) {
509                 synchronized (WeakFastHashMap.this) {
510                     map = createMap();
511                 }
512             } else {
513                 synchronized (map) {
514                     get(map).clear();
515                 }
516             }
517         }
518 
519         public boolean remove(final Object o) {
520             if (fast) {
521                 synchronized (WeakFastHashMap.this) {
522                     final Map<K, V> temp = cloneMap(map);
523                     final boolean r = get(temp).remove(o);
524                     map = temp;
525                     return r;
526                 }
527             } else {
528                 synchronized (map) {
529                     return get(map).remove(o);
530                 }
531             }
532         }
533 
534         public boolean removeAll(final Collection<?> o) {
535             if (fast) {
536                 synchronized (WeakFastHashMap.this) {
537                     final Map<K, V> temp = cloneMap(map);
538                     final boolean r = get(temp).removeAll(o);
539                     map = temp;
540                     return r;
541                 }
542             } else {
543                 synchronized (map) {
544                     return get(map).removeAll(o);
545                 }
546             }
547         }
548 
549         public boolean retainAll(final Collection<?> o) {
550             if (fast) {
551                 synchronized (WeakFastHashMap.this) {
552                     final Map<K, V> temp = cloneMap(map);
553                     final boolean r = get(temp).retainAll(o);
554                     map = temp;
555                     return r;
556                 }
557             } else {
558                 synchronized (map) {
559                     return get(map).retainAll(o);
560                 }
561             }
562         }
563 
564         public int size() {
565             if (fast) {
566                 return get(map).size();
567             } else {
568                 synchronized (map) {
569                     return get(map).size();
570                 }
571             }
572         }
573 
574 
575         public boolean isEmpty() {
576             if (fast) {
577                 return get(map).isEmpty();
578             } else {
579                 synchronized (map) {
580                     return get(map).isEmpty();
581                 }
582             }
583         }
584 
585         public boolean contains(final Object o) {
586             if (fast) {
587                 return get(map).contains(o);
588             } else {
589                 synchronized (map) {
590                     return get(map).contains(o);
591                 }
592             }
593         }
594 
595         public boolean containsAll(final Collection<?> o) {
596             if (fast) {
597                 return get(map).containsAll(o);
598             } else {
599                 synchronized (map) {
600                     return get(map).containsAll(o);
601                 }
602             }
603         }
604 
605         public <T> T[] toArray(final T[] o) {
606             if (fast) {
607                 return get(map).toArray(o);
608             } else {
609                 synchronized (map) {
610                     return get(map).toArray(o);
611                 }
612             }
613         }
614 
615         public Object[] toArray() {
616             if (fast) {
617                 return get(map).toArray();
618             } else {
619                 synchronized (map) {
620                     return get(map).toArray();
621                 }
622             }
623         }
624 
625 
626         @Override
627         public boolean equals(final Object o) {
628             if (o == this) {
629                 return true;
630             }
631             if (fast) {
632                 return get(map).equals(o);
633             } else {
634                 synchronized (map) {
635                     return get(map).equals(o);
636                 }
637             }
638         }
639 
640         @Override
641         public int hashCode() {
642             if (fast) {
643                 return get(map).hashCode();
644             } else {
645                 synchronized (map) {
646                     return get(map).hashCode();
647                 }
648             }
649         }
650 
651         public boolean add(final E o) {
652             throw new UnsupportedOperationException();
653         }
654 
655         public boolean addAll(final Collection<? extends E> c) {
656             throw new UnsupportedOperationException();
657         }
658 
659         public Iterator<E> iterator() {
660             return new CollectionViewIterator();
661         }
662 
663         private class CollectionViewIterator implements Iterator<E> {
664 
665             private Map<K, V> expected;
666             private Map.Entry<K, V> lastReturned = null;
667             private final Iterator<Map.Entry<K, V>> iterator;
668 
669             public CollectionViewIterator() {
670                 this.expected = map;
671                 this.iterator = expected.entrySet().iterator();
672             }
673 
674             public boolean hasNext() {
675                 if (expected != map) {
676                     throw new ConcurrentModificationException();
677                 }
678                 return iterator.hasNext();
679             }
680 
681             public E next() {
682                 if (expected != map) {
683                     throw new ConcurrentModificationException();
684                 }
685                 lastReturned = iterator.next();
686                 return iteratorNext(lastReturned);
687             }
688 
689             public void remove() {
690                 if (lastReturned == null) {
691                     throw new IllegalStateException();
692                 }
693                 if (fast) {
694                     synchronized (WeakFastHashMap.this) {
695                         if (expected != map) {
696                             throw new ConcurrentModificationException();
697                         }
698                         WeakFastHashMap.this.remove(lastReturned.getKey());
699                         lastReturned = null;
700                         expected = map;
701                     }
702                 } else {
703                     iterator.remove();
704                     lastReturned = null;
705                 }
706             }
707         }
708     }
709 
710     /**
711      * Set implementation over the keys of the FastHashMap
712      */
713     private class KeySet extends CollectionView<K> implements Set<K> {
714 
715         @Override
716         protected Collection<K> get(final Map<K, V> map) {
717             return map.keySet();
718         }
719 
720         @Override
721         protected K iteratorNext(final Map.Entry<K, V> entry) {
722             return entry.getKey();
723         }
724 
725     }
726 
727     /**
728      * Collection implementation over the values of the FastHashMap
729      */
730     private class Values extends CollectionView<V> {
731 
732         @Override
733         protected Collection<V> get(final Map<K, V> map) {
734             return map.values();
735         }
736 
737         @Override
738         protected V iteratorNext(final Map.Entry<K, V> entry) {
739             return entry.getValue();
740         }
741     }
742 
743     /**
744      * Set implementation over the entries of the FastHashMap
745      */
746     private class EntrySet extends CollectionView<Map.Entry<K, V>> implements Set<Map.Entry<K, V>> {
747 
748         @Override
749         protected Collection<Map.Entry<K, V>> get(final Map<K, V> map) {
750             return map.entrySet();
751         }
752 
753         @Override
754         protected Map.Entry<K, V> iteratorNext(final Map.Entry<K, V> entry) {
755             return entry;
756         }
757 
758     }
759 
760 }