001/*
002 * Copyright (c) 2010-2024 Mark Allen, Norbert Bartels.
003 *
004 * Permission is hereby granted, free of charge, to any person obtaining a copy
005 * of this software and associated documentation files (the "Software"), to deal
006 * in the Software without restriction, including without limitation the rights
007 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
008 * copies of the Software, and to permit persons to whom the Software is
009 * furnished to do so, subject to the following conditions:
010 *
011 * The above copyright notice and this permission notice shall be included in
012 * all copies or substantial portions of the Software.
013 *
014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
017 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
019 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
020 * THE SOFTWARE.
021 */
022/**
023 * This class is taken with friendly permission to use it 
024 * from <a href="http://javaspecialists.co.za/archive/Issue098.html">javaspecialists.co.za/archive/Issue098.html</a> (section 'New SoftHashMap')
025 */
026package com.restfb.util;
027
028import java.io.Serializable;
029import java.lang.ref.Reference;
030import java.lang.ref.ReferenceQueue;
031import java.lang.ref.SoftReference;
032import java.util.*;
033
034public class SoftHashMap<K, V> extends AbstractMap<K, V>implements Serializable {
035  
036  private static final long serialVersionUID = 1L;
037  
038  /** The internal HashMap that will hold the SoftReference. */
039  private final Map<K, SoftReference<V>> hash = new HashMap<>();
040
041  private final Map<SoftReference<V>, K> reverseLookup = new HashMap<>();
042
043  /** Reference queue for cleared SoftReference objects. */
044  private final ReferenceQueue<V> queue = new ReferenceQueue<>();
045
046  @Override
047  public V get(Object key) {
048    expungeStaleEntries();
049    V result = null;
050    // We get the SoftReference represented by that key
051    SoftReference<V> softRef = hash.get(key);
052    if (softRef != null) {
053      // From the SoftReference we get the value, which can be
054      // null if it has been garbage collected
055      result = softRef.get();
056      if (result == null) {
057        // If the value has been garbage collected, remove the
058        // entry from the HashMap.
059        hash.remove(key);
060        reverseLookup.remove(softRef);
061      }
062    }
063    return result;
064  }
065
066  private void expungeStaleEntries() {
067    Reference<? extends V> sv;
068    while ((sv = queue.poll()) != null) {
069      hash.remove(reverseLookup.remove(sv));
070    }
071  }
072
073  @Override
074  public V put(K key, V value) {
075    expungeStaleEntries();
076    SoftReference<V> softRef = new SoftReference<>(value, queue);
077    reverseLookup.put(softRef, key);
078    SoftReference<V> result = hash.put(key, softRef);
079    if (result == null) {
080      return null;
081    }
082    reverseLookup.remove(result);
083    return result.get();
084  }
085
086  @Override
087  public V remove(Object key) {
088    expungeStaleEntries();
089    return Optional.ofNullable(hash.remove(key)).map(SoftReference::get).orElse(null);
090  }
091
092  @Override
093  public void clear() {
094    hash.clear();
095    reverseLookup.clear();
096  }
097
098  @Override
099  public int size() {
100    expungeStaleEntries();
101    return hash.size();
102  }
103
104  /**
105   * Returns a copy of the key/values in the map at the point of calling. However, setValue still sets the value in the
106   * actual SoftHashMap.
107   */
108  @Override
109  public Set<Entry<K, V>> entrySet() {
110    expungeStaleEntries();
111    Set<Entry<K, V>> result = new LinkedHashSet<>();
112    for (final Entry<K, SoftReference<V>> entry : hash.entrySet()) {
113      final V value = entry.getValue().get();
114      if (value != null) {
115        result.add(new Entry<K, V>() {
116          @Override
117          public K getKey() {
118            return entry.getKey();
119          }
120
121          @Override
122          public V getValue() {
123            return value;
124          }
125
126          @Override
127          public V setValue(V v) {
128            entry.setValue(new SoftReference<>(v, queue));
129            return value;
130          }
131        });
132      }
133    }
134    return result;
135  }
136}