| <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../../jacoco-resources/report.gif" type="image/gif"/><title>SoftHashMap.java</title><link rel="stylesheet" href="../../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../../index.html" class="el_report">Apache Shiro :: Test Coverage</a> > <a href="../index.html" class="el_bundle">shiro-lang</a> > <a href="index.source.html" class="el_package">org.apache.shiro.util</a> > <span class="el_source">SoftHashMap.java</span></div><h1>SoftHashMap.java</h1><pre class="source lang-java linenums">/* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.shiro.util; |
| |
| import java.lang.ref.ReferenceQueue; |
| import java.lang.ref.SoftReference; |
| import java.util.*; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentLinkedQueue; |
| import java.util.concurrent.locks.ReentrantLock; |
| |
| |
| /** |
| * A <code><em>Soft</em>HashMap</code> is a memory-constrained map that stores its <em>values</em> in |
| * {@link SoftReference SoftReference}s. (Contrast this with the JDK's |
| * {@link WeakHashMap WeakHashMap}, which uses weak references for its <em>keys</em>, which is of little value if you |
| * want the cache to auto-resize itself based on memory constraints). |
| * <p/> |
| * Having the values wrapped by soft references allows the cache to automatically reduce its size based on memory |
| * limitations and garbage collection. This ensures that the cache will not cause memory leaks by holding strong |
| * references to all of its values. |
| * <p/> |
| * This class is a generics-enabled Map based on initial ideas from Heinz Kabutz's and Sydney Redelinghuys's |
| * <a href="http://www.javaspecialists.eu/archive/Issue015.html">publicly posted version (with their approval)</a>, with |
| * continued modifications. |
| * <p/> |
| * This implementation is thread-safe and usable in concurrent environments. |
| * |
| * @since 1.0 |
| */ |
| public class SoftHashMap<K, V> implements Map<K, V> { |
| |
| /** |
| * The default value of the RETENTION_SIZE attribute, equal to 100. |
| */ |
| private static final int DEFAULT_RETENTION_SIZE = 100; |
| |
| /** |
| * The internal HashMap that will hold the SoftReference. |
| */ |
| private final Map<K, SoftValue<V, K>> map; |
| |
| /** |
| * The number of strong references to hold internally, that is, the number of instances to prevent |
| * from being garbage collected automatically (unlike other soft references). |
| */ |
| private final int RETENTION_SIZE; |
| |
| /** |
| * The FIFO list of strong references (not to be garbage collected), order of last access. |
| */ |
| private final Queue<V> strongReferences; //guarded by 'strongReferencesLock' |
| private final ReentrantLock strongReferencesLock; |
| |
| /** |
| * Reference queue for cleared SoftReference objects. |
| */ |
| private final ReferenceQueue<? super V> queue; |
| |
| /** |
| * Creates a new SoftHashMap with a default retention size size of |
| * {@link #DEFAULT_RETENTION_SIZE DEFAULT_RETENTION_SIZE} (100 entries). |
| * |
| * @see #SoftHashMap(int) |
| */ |
| public SoftHashMap() { |
| <span class="fc" id="L83"> this(DEFAULT_RETENTION_SIZE);</span> |
| <span class="fc" id="L84"> }</span> |
| |
| /** |
| * Creates a new SoftHashMap with the specified retention size. |
| * <p/> |
| * The retention size (n) is the total number of most recent entries in the map that will be strongly referenced |
| * (ie 'retained') to prevent them from being eagerly garbage collected. That is, the point of a SoftHashMap is to |
| * allow the garbage collector to remove as many entries from this map as it desires, but there will always be (n) |
| * elements retained after a GC due to the strong references. |
| * <p/> |
| * Note that in a highly concurrent environments the exact total number of strong references may differ slightly |
| * than the actual <code>retentionSize</code> value. This number is intended to be a best-effort retention low |
| * water mark. |
| * |
| * @param retentionSize the total number of most recent entries in the map that will be strongly referenced |
| * (retained), preventing them from being eagerly garbage collected by the JVM. |
| */ |
| @SuppressWarnings({"unchecked"}) |
| public SoftHashMap(int retentionSize) { |
| <span class="fc" id="L103"> super();</span> |
| <span class="fc" id="L104"> RETENTION_SIZE = Math.max(0, retentionSize);</span> |
| <span class="fc" id="L105"> queue = new ReferenceQueue<V>();</span> |
| <span class="fc" id="L106"> strongReferencesLock = new ReentrantLock();</span> |
| <span class="fc" id="L107"> map = new ConcurrentHashMap<K, SoftValue<V, K>>();</span> |
| <span class="fc" id="L108"> strongReferences = new ConcurrentLinkedQueue<V>();</span> |
| <span class="fc" id="L109"> }</span> |
| |
| /** |
| * Creates a {@code SoftHashMap} backed by the specified {@code source}, with a default retention |
| * size of {@link #DEFAULT_RETENTION_SIZE DEFAULT_RETENTION_SIZE} (100 entries). |
| * |
| * @param source the backing map to populate this {@code SoftHashMap} |
| * @see #SoftHashMap(Map,int) |
| */ |
| public SoftHashMap(Map<K, V> source) { |
| <span class="nc" id="L119"> this(DEFAULT_RETENTION_SIZE);</span> |
| <span class="nc" id="L120"> putAll(source);</span> |
| <span class="nc" id="L121"> }</span> |
| |
| /** |
| * Creates a {@code SoftHashMap} backed by the specified {@code source}, with the specified retention size. |
| * <p/> |
| * The retention size (n) is the total number of most recent entries in the map that will be strongly referenced |
| * (ie 'retained') to prevent them from being eagerly garbage collected. That is, the point of a SoftHashMap is to |
| * allow the garbage collector to remove as many entries from this map as it desires, but there will always be (n) |
| * elements retained after a GC due to the strong references. |
| * <p/> |
| * Note that in a highly concurrent environments the exact total number of strong references may differ slightly |
| * than the actual <code>retentionSize</code> value. This number is intended to be a best-effort retention low |
| * water mark. |
| * |
| * @param source the backing map to populate this {@code SoftHashMap} |
| * @param retentionSize the total number of most recent entries in the map that will be strongly referenced |
| * (retained), preventing them from being eagerly garbage collected by the JVM. |
| */ |
| public SoftHashMap(Map<K, V> source, int retentionSize) { |
| <span class="nc" id="L140"> this(retentionSize);</span> |
| <span class="nc" id="L141"> putAll(source);</span> |
| <span class="nc" id="L142"> }</span> |
| |
| public V get(Object key) { |
| <span class="fc" id="L145"> processQueue();</span> |
| |
| <span class="fc" id="L147"> V result = null;</span> |
| <span class="fc" id="L148"> SoftValue<V, K> value = map.get(key);</span> |
| |
| <span class="fc bfc" id="L150" title="All 2 branches covered."> if (value != null) {</span> |
| //unwrap the 'real' value from the SoftReference |
| <span class="fc" id="L152"> result = value.get();</span> |
| <span class="pc bpc" id="L153" title="1 of 2 branches missed."> if (result == null) {</span> |
| //The wrapped value was garbage collected, so remove this entry from the backing map: |
| //noinspection SuspiciousMethodCalls |
| <span class="nc" id="L156"> map.remove(key);</span> |
| } else { |
| //Add this value to the beginning of the strong reference queue (FIFO). |
| <span class="fc" id="L159"> addToStrongReferences(result);</span> |
| } |
| } |
| <span class="fc" id="L162"> return result;</span> |
| } |
| |
| private void addToStrongReferences(V result) { |
| <span class="fc" id="L166"> strongReferencesLock.lock();</span> |
| try { |
| <span class="fc" id="L168"> strongReferences.add(result);</span> |
| <span class="fc" id="L169"> trimStrongReferencesIfNecessary();</span> |
| } finally { |
| <span class="fc" id="L171"> strongReferencesLock.unlock();</span> |
| } |
| |
| <span class="fc" id="L174"> }</span> |
| |
| //Guarded by the strongReferencesLock in the addToStrongReferences method |
| |
| private void trimStrongReferencesIfNecessary() { |
| //trim the strong ref queue if necessary: |
| <span class="pc bpc" id="L180" title="1 of 2 branches missed."> while (strongReferences.size() > RETENTION_SIZE) {</span> |
| <span class="nc" id="L181"> strongReferences.poll();</span> |
| } |
| <span class="fc" id="L183"> }</span> |
| |
| /** |
| * Traverses the ReferenceQueue and removes garbage-collected SoftValue objects from the backing map |
| * by looking them up using the SoftValue.key data member. |
| */ |
| private void processQueue() { |
| SoftValue sv; |
| <span class="pc bpc" id="L191" title="1 of 2 branches missed."> while ((sv = (SoftValue) queue.poll()) != null) {</span> |
| //noinspection SuspiciousMethodCalls |
| <span class="nc" id="L193"> map.remove(sv.key); // we can access private data!</span> |
| } |
| <span class="fc" id="L195"> }</span> |
| |
| public boolean isEmpty() { |
| <span class="nc" id="L198"> processQueue();</span> |
| <span class="nc" id="L199"> return map.isEmpty();</span> |
| } |
| |
| public boolean containsKey(Object key) { |
| <span class="nc" id="L203"> processQueue();</span> |
| <span class="nc" id="L204"> return map.containsKey(key);</span> |
| } |
| |
| public boolean containsValue(Object value) { |
| <span class="nc" id="L208"> processQueue();</span> |
| <span class="nc" id="L209"> Collection values = values();</span> |
| <span class="nc bnc" id="L210" title="All 4 branches missed."> return values != null && values.contains(value);</span> |
| } |
| |
| public void putAll(Map<? extends K, ? extends V> m) { |
| <span class="nc bnc" id="L214" title="All 4 branches missed."> if (m == null || m.isEmpty()) {</span> |
| <span class="nc" id="L215"> processQueue();</span> |
| <span class="nc" id="L216"> return;</span> |
| } |
| <span class="nc bnc" id="L218" title="All 2 branches missed."> for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {</span> |
| <span class="nc" id="L219"> put(entry.getKey(), entry.getValue());</span> |
| <span class="nc" id="L220"> }</span> |
| <span class="nc" id="L221"> }</span> |
| |
| public Set<K> keySet() { |
| <span class="nc" id="L224"> processQueue();</span> |
| <span class="nc" id="L225"> return map.keySet();</span> |
| } |
| |
| public Collection<V> values() { |
| <span class="nc" id="L229"> processQueue();</span> |
| <span class="nc" id="L230"> Collection<K> keys = map.keySet();</span> |
| <span class="nc bnc" id="L231" title="All 2 branches missed."> if (keys.isEmpty()) {</span> |
| //noinspection unchecked |
| <span class="nc" id="L233"> return Collections.EMPTY_SET;</span> |
| } |
| <span class="nc" id="L235"> Collection<V> values = new ArrayList<V>(keys.size());</span> |
| <span class="nc bnc" id="L236" title="All 2 branches missed."> for (K key : keys) {</span> |
| <span class="nc" id="L237"> V v = get(key);</span> |
| <span class="nc bnc" id="L238" title="All 2 branches missed."> if (v != null) {</span> |
| <span class="nc" id="L239"> values.add(v);</span> |
| } |
| <span class="nc" id="L241"> }</span> |
| <span class="nc" id="L242"> return values;</span> |
| } |
| |
| /** |
| * Creates a new entry, but wraps the value in a SoftValue instance to enable auto garbage collection. |
| */ |
| public V put(K key, V value) { |
| <span class="fc" id="L249"> processQueue(); // throw out garbage collected values first</span> |
| <span class="fc" id="L250"> SoftValue<V, K> sv = new SoftValue<V, K>(value, key, queue);</span> |
| <span class="fc" id="L251"> SoftValue<V, K> previous = map.put(key, sv);</span> |
| <span class="fc" id="L252"> addToStrongReferences(value);</span> |
| <span class="pc bpc" id="L253" title="1 of 2 branches missed."> return previous != null ? previous.get() : null;</span> |
| } |
| |
| public V remove(Object key) { |
| <span class="fc" id="L257"> processQueue(); // throw out garbage collected values first</span> |
| <span class="fc" id="L258"> SoftValue<V, K> raw = map.remove(key);</span> |
| <span class="pc bpc" id="L259" title="1 of 2 branches missed."> return raw != null ? raw.get() : null;</span> |
| } |
| |
| public void clear() { |
| <span class="nc" id="L263"> strongReferencesLock.lock();</span> |
| try { |
| <span class="nc" id="L265"> strongReferences.clear();</span> |
| } finally { |
| <span class="nc" id="L267"> strongReferencesLock.unlock();</span> |
| } |
| <span class="nc" id="L269"> processQueue(); // throw out garbage collected values</span> |
| <span class="nc" id="L270"> map.clear();</span> |
| <span class="nc" id="L271"> }</span> |
| |
| public int size() { |
| <span class="nc" id="L274"> processQueue(); // throw out garbage collected values first</span> |
| <span class="nc" id="L275"> return map.size();</span> |
| } |
| |
| public Set<Map.Entry<K, V>> entrySet() { |
| <span class="nc" id="L279"> processQueue(); // throw out garbage collected values first</span> |
| <span class="nc" id="L280"> Collection<K> keys = map.keySet();</span> |
| <span class="nc bnc" id="L281" title="All 2 branches missed."> if (keys.isEmpty()) {</span> |
| //noinspection unchecked |
| <span class="nc" id="L283"> return Collections.EMPTY_SET;</span> |
| } |
| |
| <span class="nc" id="L286"> Map<K, V> kvPairs = new HashMap<K, V>(keys.size());</span> |
| <span class="nc bnc" id="L287" title="All 2 branches missed."> for (K key : keys) {</span> |
| <span class="nc" id="L288"> V v = get(key);</span> |
| <span class="nc bnc" id="L289" title="All 2 branches missed."> if (v != null) {</span> |
| <span class="nc" id="L290"> kvPairs.put(key, v);</span> |
| } |
| <span class="nc" id="L292"> }</span> |
| <span class="nc" id="L293"> return kvPairs.entrySet();</span> |
| } |
| |
| /** |
| * We define our own subclass of SoftReference which contains |
| * not only the value but also the key to make it easier to find |
| * the entry in the HashMap after it's been garbage collected. |
| */ |
| private static class SoftValue<V, K> extends SoftReference<V> { |
| |
| private final K key; |
| |
| /** |
| * Constructs a new instance, wrapping the value, key, and queue, as |
| * required by the superclass. |
| * |
| * @param value the map value |
| * @param key the map key |
| * @param queue the soft reference queue to poll to determine if the entry had been reaped by the GC. |
| */ |
| private SoftValue(V value, K key, ReferenceQueue<? super V> queue) { |
| <span class="fc" id="L314"> super(value, queue);</span> |
| <span class="fc" id="L315"> this.key = key;</span> |
| <span class="fc" id="L316"> }</span> |
| |
| } |
| } |
| </pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.3.201901230119</span></div></body></html> |