001package org.apache.commons.jcs3.engine.memory.soft;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.IOException;
023import java.lang.ref.SoftReference;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028import java.util.concurrent.ConcurrentHashMap;
029import java.util.concurrent.ConcurrentMap;
030import java.util.concurrent.LinkedBlockingQueue;
031
032import org.apache.commons.jcs3.engine.behavior.ICacheElement;
033import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
034import org.apache.commons.jcs3.engine.control.CompositeCache;
035import org.apache.commons.jcs3.engine.memory.AbstractMemoryCache;
036import org.apache.commons.jcs3.engine.memory.util.MemoryElementDescriptor;
037import org.apache.commons.jcs3.engine.memory.util.SoftReferenceElementDescriptor;
038import org.apache.commons.jcs3.engine.stats.StatElement;
039import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
040import org.apache.commons.jcs3.engine.stats.behavior.IStats;
041import org.apache.commons.jcs3.log.Log;
042import org.apache.commons.jcs3.log.LogManager;
043
044/**
045 * A JCS IMemoryCache that has {@link SoftReference} to all its values.
046 * This cache does not respect {@link ICompositeCacheAttributes#getMaxObjects()}
047 * as overflowing is handled by Java GC.
048 * <p>
049 * The cache also has strong references to a maximum number of objects given by
050 * the maxObjects parameter
051 * </p>
052 */
053public class SoftReferenceMemoryCache<K, V> extends AbstractMemoryCache<K, V>
054{
055    /** The logger. */
056    private static final Log log = LogManager.getLog(SoftReferenceMemoryCache.class);
057
058    /**
059     * Strong references to the maxObjects number of newest objects.
060     * <p>
061     * Trimming is done by {@link #trimStrongReferences()} instead of by
062     * overriding removeEldestEntry to be able to control waterfalling as easy
063     * as possible
064     */
065    private LinkedBlockingQueue<ICacheElement<K, V>> strongReferences;
066
067    /**
068     * For post reflection creation initialization
069     * <p>
070     * @param hub
071     */
072    @Override
073    public synchronized void initialize( final CompositeCache<K, V> hub )
074    {
075        super.initialize( hub );
076        strongReferences = new LinkedBlockingQueue<>();
077        log.info( "initialized Soft Reference Memory Cache for {0}",
078                this::getCacheName );
079    }
080
081    /**
082     * @see org.apache.commons.jcs3.engine.memory.AbstractMemoryCache#createMap()
083     */
084    @Override
085    public ConcurrentMap<K, MemoryElementDescriptor<K, V>> createMap()
086    {
087        return new ConcurrentHashMap<>();
088    }
089
090    /**
091     * @see org.apache.commons.jcs3.engine.memory.behavior.IMemoryCache#getKeySet()
092     */
093    @Override
094    public Set<K> getKeySet()
095    {
096        final Set<K> keys = new HashSet<>();
097        for (final Map.Entry<K, MemoryElementDescriptor<K, V>> e : map.entrySet())
098        {
099            final SoftReferenceElementDescriptor<K, V> sred = (SoftReferenceElementDescriptor<K, V>) e.getValue();
100            if (sred.getCacheElement() != null)
101            {
102                keys.add(e.getKey());
103            }
104        }
105
106        return keys;
107    }
108
109    /**
110     * Returns the current cache size.
111     * <p>
112     * @return The size value
113     */
114    @Override
115    public int getSize()
116    {
117        int size = 0;
118        for (final MemoryElementDescriptor<K, V> me : map.values())
119        {
120            final SoftReferenceElementDescriptor<K, V> sred = (SoftReferenceElementDescriptor<K, V>) me;
121            if (sred.getCacheElement() != null)
122            {
123                size++;
124            }
125        }
126        return size;
127    }
128
129    /**
130     * @return statistics about the cache
131     */
132    @Override
133    public IStats getStatistics()
134    {
135        final IStats stats = super.getStatistics();
136        stats.setTypeName("Soft Reference Memory Cache");
137
138        final List<IStatElement<?>> elems = stats.getStatElements();
139        final int emptyrefs = map.size() - getSize();
140        elems.add(new StatElement<>("Empty References", Integer.valueOf(emptyrefs)));
141        elems.add(new StatElement<>("Strong References", Integer.valueOf(strongReferences.size())));
142
143        return stats;
144    }
145
146    /**
147     * Update control structures after get
148     * (guarded by the lock)
149     *
150     * @param me the memory element descriptor
151     */
152    @Override
153    protected void lockedGetElement(final MemoryElementDescriptor<K, V> me)
154    {
155        final ICacheElement<K, V> val = me.getCacheElement();
156        val.getElementAttributes().setLastAccessTimeNow();
157
158        // update the ordering of the strong references
159        strongReferences.add(val);
160        trimStrongReferences();
161    }
162
163    /**
164     * Remove element from control structure
165     * (guarded by the lock)
166     *
167     * @param me the memory element descriptor
168     */
169    @Override
170    protected void lockedRemoveElement(final MemoryElementDescriptor<K, V> me)
171    {
172        strongReferences.remove(me.getCacheElement());
173    }
174
175    /**
176     * Removes all cached items from the cache control structures.
177     * (guarded by the lock)
178     */
179    @Override
180    protected void lockedRemoveAll()
181    {
182        strongReferences.clear();
183    }
184
185    /**
186     * Puts an item to the cache.
187     * <p>
188     * @param ce Description of the Parameter
189     * @throws IOException Description of the Exception
190     */
191    @Override
192    public void update(final ICacheElement<K, V> ce) throws IOException
193    {
194        putCnt.incrementAndGet();
195        ce.getElementAttributes().setLastAccessTimeNow();
196
197        lock.lock();
198
199        try
200        {
201            map.put(ce.getKey(), new SoftReferenceElementDescriptor<>(ce));
202            strongReferences.add(ce);
203            trimStrongReferences();
204        }
205        finally
206        {
207            lock.unlock();
208        }
209    }
210
211    /**
212     * Trim the number of strong references to equal or below the number given
213     * by the maxObjects parameter.
214     */
215    private void trimStrongReferences()
216    {
217        final int max = getCacheAttributes().getMaxObjects();
218        final int startsize = strongReferences.size();
219
220        for (int cursize = startsize; cursize > max; cursize--)
221        {
222            final ICacheElement<K, V> ce = strongReferences.poll();
223            waterfal(ce);
224        }
225    }
226
227    /**
228     * This can't be implemented.
229     * <p>
230     * @param numberToFree
231     * @return 0
232     * @throws IOException
233     */
234    @Override
235    public int freeElements(final int numberToFree) throws IOException
236    {
237        return 0;
238    }
239}