blob: 208f2440f880c95688fc577427b75b207556fa37 [file] [log] [blame]
<?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 :: All (aggregate jar)</a> &gt; <a href="../index.html" class="el_bundle">shiro-lang</a> &gt; <a href="index.source.html" class="el_package">org.apache.shiro.util</a> &gt; <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
* &quot;License&quot;); 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
* &quot;AS IS&quot; 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 &lt;code&gt;&lt;em&gt;Soft&lt;/em&gt;HashMap&lt;/code&gt; is a memory-constrained map that stores its &lt;em&gt;values&lt;/em&gt; in
* {@link SoftReference SoftReference}s. (Contrast this with the JDK's
* {@link WeakHashMap WeakHashMap}, which uses weak references for its &lt;em&gt;keys&lt;/em&gt;, which is of little value if you
* want the cache to auto-resize itself based on memory constraints).
* &lt;p/&gt;
* 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.
* &lt;p/&gt;
* This class is a generics-enabled Map based on initial ideas from Heinz Kabutz's and Sydney Redelinghuys's
* &lt;a href=&quot;http://www.javaspecialists.eu/archive/Issue015.html&quot;&gt;publicly posted version (with their approval)&lt;/a&gt;, with
* continued modifications.
* &lt;p/&gt;
* This implementation is thread-safe and usable in concurrent environments.
*
* @since 1.0
*/
public class SoftHashMap&lt;K, V&gt; implements Map&lt;K, V&gt; {
/**
* 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&lt;K, SoftValue&lt;V, K&gt;&gt; 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&lt;V&gt; strongReferences; //guarded by 'strongReferencesLock'
private final ReentrantLock strongReferencesLock;
/**
* Reference queue for cleared SoftReference objects.
*/
private final ReferenceQueue&lt;? super V&gt; 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.
* &lt;p/&gt;
* 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.
* &lt;p/&gt;
* Note that in a highly concurrent environments the exact total number of strong references may differ slightly
* than the actual &lt;code&gt;retentionSize&lt;/code&gt; 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({&quot;unchecked&quot;})
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&lt;V&gt;();</span>
<span class="fc" id="L106"> strongReferencesLock = new ReentrantLock();</span>
<span class="fc" id="L107"> map = new ConcurrentHashMap&lt;K, SoftValue&lt;V, K&gt;&gt;();</span>
<span class="fc" id="L108"> strongReferences = new ConcurrentLinkedQueue&lt;V&gt;();</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&lt;K, V&gt; 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.
* &lt;p/&gt;
* 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.
* &lt;p/&gt;
* Note that in a highly concurrent environments the exact total number of strong references may differ slightly
* than the actual &lt;code&gt;retentionSize&lt;/code&gt; 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&lt;K, V&gt; 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&lt;V, K&gt; 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="pc" id="L171"> strongReferencesLock.unlock();</span>
<span class="fc" id="L172"> }</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() &gt; 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 &amp;&amp; values.contains(value);</span>
}
public void putAll(Map&lt;? extends K, ? extends V&gt; 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&lt;? extends K, ? extends V&gt; 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&lt;K&gt; keySet() {
<span class="nc" id="L224"> processQueue();</span>
<span class="nc" id="L225"> return map.keySet();</span>
}
public Collection&lt;V&gt; values() {
<span class="nc" id="L229"> processQueue();</span>
<span class="nc" id="L230"> Collection&lt;K&gt; 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&lt;V&gt; values = new ArrayList&lt;V&gt;(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&lt;V, K&gt; sv = new SoftValue&lt;V, K&gt;(value, key, queue);</span>
<span class="fc" id="L251"> SoftValue&lt;V, K&gt; 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&lt;V, K&gt; 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="L268"> }</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&lt;Map.Entry&lt;K, V&gt;&gt; entrySet() {
<span class="nc" id="L279"> processQueue(); // throw out garbage collected values first</span>
<span class="nc" id="L280"> Collection&lt;K&gt; 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&lt;K, V&gt; kvPairs = new HashMap&lt;K, V&gt;(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&lt;V, K&gt; extends SoftReference&lt;V&gt; {
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&lt;? super V&gt; 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.eclemma.org/jacoco">JaCoCo</a> 0.7.7.201606060606</span></div></body></html>