View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.util;
20  
21  import java.lang.ref.Reference;
22  import java.lang.ref.ReferenceQueue;
23  import java.lang.ref.SoftReference;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.Comparator;
28  import java.util.LinkedHashSet;
29  import java.util.Map;
30  import java.util.NavigableMap;
31  import java.util.Set;
32  import java.util.SortedMap;
33  import java.util.TreeMap;
34  
35  import org.apache.hadoop.classification.InterfaceAudience;
36  import org.apache.hadoop.classification.InterfaceStability;
37  
38  /**
39   * A SortedMap implementation that uses Soft Reference values
40   * internally to make it play well with the GC when in a low-memory
41   * situation. Use as a cache where you also need SortedMap functionality.
42   *
43   * @param <K> key class
44   * @param <V> value class
45   */
46  @InterfaceAudience.Public
47  @InterfaceStability.Stable
48  public class SoftValueSortedMap<K,V> implements SortedMap<K,V> {
49    private final SortedMap<K, SoftValue<K,V>> internalMap;
50    private final ReferenceQueue<V> rq = new ReferenceQueue<V>();
51    private final Object sync;
52  
53    /** Constructor */
54    public SoftValueSortedMap() {
55      this(new TreeMap<K, SoftValue<K,V>>());
56    }
57  
58    /**
59     * Constructor
60     * @param c comparator
61     */
62    public SoftValueSortedMap(final Comparator<K> c) {
63      this(new TreeMap<K, SoftValue<K,V>>(c));
64    }
65  
66    /** Internal constructor
67     * @param original object to wrap and synchronize on
68     */
69    private SoftValueSortedMap(SortedMap<K,SoftValue<K,V>> original) {
70      this(original, original);
71    }
72  
73    /** Internal constructor
74     * For headMap, tailMap, and subMap support
75     * @param original object to wrap
76     * @param sync object to synchronize on
77     */
78    private SoftValueSortedMap(SortedMap<K,SoftValue<K,V>> original, Object sync) {
79      this.internalMap = original;
80      this.sync = sync;
81    }
82  
83    /**
84     * Checks soft references and cleans any that have been placed on
85     * ReferenceQueue.  Call if get/put etc. are not called regularly.
86     * Internally these call checkReferences on each access.
87     * @return How many references cleared.
88     */
89    @SuppressWarnings("unchecked")
90    private int checkReferences() {
91      int i = 0;
92      for (Reference<? extends V> ref; (ref = this.rq.poll()) != null;) {
93        i++;
94        this.internalMap.remove(((SoftValue<K,V>)ref).key);
95      }
96      return i;
97    }
98  
99    public V put(K key, V value) {
100     synchronized(sync) {
101       checkReferences();
102       SoftValue<K,V> oldValue = this.internalMap.put(key,
103         new SoftValue<K,V>(key, value, this.rq));
104       return oldValue == null ? null : oldValue.get();
105     }
106   }
107 
108   @Override
109   public void putAll(Map<? extends K, ? extends V> m) {
110     throw new RuntimeException("Not implemented");
111   }
112 
113   public V get(Object key) {
114     synchronized(sync) {
115       checkReferences();
116       SoftValue<K,V> value = this.internalMap.get(key);
117       if (value == null) {
118         return null;
119       }
120       if (value.get() == null) {
121         this.internalMap.remove(key);
122         return null;
123       }
124       return value.get();
125     }
126   }
127 
128   public V remove(Object key) {
129     synchronized(sync) {
130       checkReferences();
131       SoftValue<K,V> value = this.internalMap.remove(key);
132       return value == null ? null : value.get();
133     }
134   }
135 
136   public boolean containsKey(Object key) {
137     synchronized(sync) {
138       checkReferences();
139       return this.internalMap.containsKey(key);
140     }
141   }
142 
143   public boolean containsValue(Object value) {
144     throw new UnsupportedOperationException("Don't support containsValue!");
145   }
146 
147   public K firstKey() {
148     synchronized(sync) {
149       checkReferences();
150       return internalMap.firstKey();
151     }
152   }
153 
154   public K lastKey() {
155     synchronized(sync) {
156       checkReferences();
157       return internalMap.lastKey();
158     }
159   }
160 
161   public SoftValueSortedMap<K,V> headMap(K key) {
162     synchronized(sync) {
163       checkReferences();
164       return new SoftValueSortedMap<K,V>(this.internalMap.headMap(key), sync);
165     }
166   }
167 
168   public SoftValueSortedMap<K,V> tailMap(K key) {
169     synchronized(sync) {
170       checkReferences();
171       return new SoftValueSortedMap<K,V>(this.internalMap.tailMap(key), sync);
172     }
173   }
174 
175   public SoftValueSortedMap<K,V> subMap(K fromKey, K toKey) {
176     synchronized(sync) {
177       checkReferences();
178       return new SoftValueSortedMap<K,V>(this.internalMap.subMap(fromKey,
179           toKey), sync);
180     }
181   }
182 
183   /*
184    * retrieves the value associated with the greatest key strictly less than
185    *  the given key, or null if there is no such key
186    * @param key the key we're interested in
187    */
188   public synchronized V lowerValueByKey(K key) {
189     synchronized(sync) {
190       checkReferences();
191 
192       Map.Entry<K,SoftValue<K,V>> entry =
193         ((NavigableMap<K, SoftValue<K,V>>) this.internalMap).lowerEntry(key);
194       if (entry==null) {
195         return null;
196       }
197       SoftValue<K,V> value=entry.getValue();
198       if (value==null) {
199         return null;
200       }
201       if (value.get() == null) {
202         this.internalMap.remove(key);
203         return null;
204       }
205       return value.get();
206     }
207   }
208   
209   public boolean isEmpty() {
210     synchronized(sync) {
211       checkReferences();
212       return this.internalMap.isEmpty();
213     }
214   }
215 
216   public int size() {
217     synchronized(sync) {
218       checkReferences();
219       return this.internalMap.size();
220     }
221   }
222 
223   public void clear() {
224     synchronized(sync) {
225       checkReferences();
226       this.internalMap.clear();
227     }
228   }
229 
230   public Set<K> keySet() {
231     synchronized(sync) {
232       checkReferences();
233       // this is not correct as per SortedMap contract (keySet should be
234       // modifiable)
235       // needed here so that another thread cannot modify the keyset
236       // without locking
237       return Collections.unmodifiableSet(this.internalMap.keySet());
238     }
239   }
240 
241   public Comparator<? super K> comparator() {
242     return this.internalMap.comparator();
243   }
244 
245   public Set<Map.Entry<K,V>> entrySet() {
246     synchronized(sync) {
247       checkReferences();
248       // this is not correct as per SortedMap contract (entrySet should be
249       // backed by map)
250       Set<Map.Entry<K, V>> realEntries = new LinkedHashSet<Map.Entry<K, V>>();
251       for (Map.Entry<K, SoftValue<K, V>> entry : this.internalMap.entrySet()) {
252         realEntries.add(entry.getValue());
253       }
254       return realEntries;
255     }
256   }
257 
258   public Collection<V> values() {
259     synchronized(sync) {
260       checkReferences();
261       ArrayList<V> hardValues = new ArrayList<V>();
262       for (SoftValue<K,V> softValue : this.internalMap.values()) {
263         hardValues.add(softValue.get());
264       }
265       return hardValues;
266     }
267   }
268 
269   private static class SoftValue<K,V> extends SoftReference<V> implements Map.Entry<K, V> {
270     final K key;
271 
272     SoftValue(K key, V value, ReferenceQueue<V> q) {
273       super(value, q);
274       this.key = key;
275     }
276 
277     public K getKey() {
278       return this.key;
279     }
280 
281     public V getValue() {
282       return get();
283     }
284 
285     public V setValue(V value) {
286       throw new RuntimeException("Not implemented");
287     }
288   }
289 }