001/* 
002 * JKNIV, utils - Helper utilities for jdk code.
003 * 
004 * Copyright (C) 2017, the original author or authors.
005 *
006 * This library is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 2.1 of the License.
010 * 
011 * This library is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 * 
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with this library; if not, write to the Free Software Foundation, Inc., 
018 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
019 */
020package net.sf.jkniv.cache;
021
022import java.util.Date;
023import java.util.Map;
024import java.util.Set;
025import java.util.concurrent.ConcurrentHashMap;
026import java.util.concurrent.ConcurrentMap;
027import java.util.concurrent.TimeUnit;
028
029import net.sf.jkniv.asserts.Assertable;
030import net.sf.jkniv.asserts.AssertsFactory;
031
032/**
033 * MemoryCache based implementation of the <tt>Cacheable</tt> interface. 
034 * This implementation provides a memory cache to keep the objects mapped keys to values.
035 * The <tt>Cacheable</tt> doesn't permit {@code null} values.
036 *
037 * Default policy parameters:
038 * <ul>
039 *  <li>Time-to-Live (TTL) default value is 10 minutes</li>
040 *  <li>Time-to-idle (TTI) default value is forever</li>
041 *  <li>Cache size default value is 1000 elements</li>
042 * </ul>
043 * @param <K> the type of keys maintained by this map
044 * @param <V> Type of objects stored in cache
045 * 
046 * @author Alisson Gomes
047 * @since 0.6.0
048 */
049public class MemoryCache<K, V> implements Cacheable<K, V>
050{
051    private static final Assertable    notNull = AssertsFactory.getNotNull();
052    private String                               name;
053    private CachePolicy                          policy;
054    private ConcurrentMap<K, Cacheable.Entry<V>> cache;
055    private Cacheable.Entry<V> minorHit;
056    private K keyMinorHit;
057    
058    public MemoryCache()
059    {
060        this(new TTLCachePolicy(TimeUnit.MINUTES.toSeconds(10L)));
061    }
062    
063    public MemoryCache(String name)
064    {
065        this(new TTLCachePolicy(TimeUnit.MINUTES.toSeconds(10L)), name);
066    }
067    
068    public MemoryCache(CachePolicy policy)
069    {
070        this(policy, "nonamed");
071    }
072    
073    public MemoryCache(CachePolicy policy, String name)
074    {
075        this.name = name;
076        this.policy = policy;
077        this.cache = new ConcurrentHashMap<K, Cacheable.Entry<V>>();
078    }
079    
080    /* (non-Javadoc)
081     * @see net.sf.jkniv.cache.Cacheable#getName()
082     */
083    @Override
084    public String getName()
085    {
086        return name;
087    }
088    
089    /* (non-Javadoc)
090     * @see net.sf.jkniv.cache.Cacheable#getPolicy()
091     */
092    @Override
093    public CachePolicy getPolicy()
094    {
095        return policy;
096    }
097
098    @Override
099    public void setPolicy(CachePolicy policy)
100    {
101        this.policy = policy;
102    }
103    
104    /* (non-Javadoc)
105     * @see net.sf.jkniv.cache.Cacheable#put(java.lang.String, T)
106     */
107    @Override
108    public V put(K key, V object)
109    {
110        notNull.verify(key, object);
111        Cacheable.Entry<V> entry = new MemoryCache.Entry<V>(object);
112        if (this.cache.size() >= policy.size())
113        {
114            if (this.keyMinorHit != null)
115                remove(this.keyMinorHit);
116        }
117        else
118        {
119            Cacheable.Entry<V> old = this.cache.put(key, entry);
120            if (old != null)
121                return old.getValue();
122        }
123        return null;
124    }
125    
126    /* (non-Javadoc)
127     * @see net.sf.jkniv.cache.Cacheable#get(java.lang.String)
128     */
129    @Override
130    public V get(K key)
131    {
132        Cacheable.Entry<V> entry = getEntry(key);
133        if (entry == null )
134            return null;
135        
136        return entry.getValue();
137    }
138    
139    public Cacheable.Entry<V> getEntry(K key)
140    {
141        Cacheable.Entry<V> entry = this.cache.get(key);
142        
143        if (entry == null )
144            return null;
145
146        markMinorHit(entry);
147        
148        if (policy.isAlive(entry.getTimestamp().getTime(), entry.getLastAccess().getTime()))
149            return entry;
150        
151        this.cache.remove(key);
152        return null;
153    }
154    
155    private void markMinorHit(Cacheable.Entry<V> entry)
156    {
157        if (minorHit == null)
158            minorHit = entry;
159        else
160        {
161            if(minorHit.hits() > entry.hits())
162                minorHit = entry;
163        }
164    }
165    
166    public Set<Map.Entry<K, Cacheable.Entry<V>>> entrySet()
167    {
168        return this.cache.entrySet();
169    }
170    
171    public Cacheable.Entry<V> remove(K key)
172    {
173        return this.cache.remove(key);
174    }
175    
176    @Override
177    public void clear()
178    {
179        this.cache.clear();
180        this.minorHit = null;
181    }
182    
183    @Override
184    public long size()
185    {
186        return this.cache.size();
187    }
188    
189    @Override
190    public String toString()
191    {
192        return "MemoryCache [name=" + name + ", policy=" + policy + ", cacheSize=" + cache.size() + "]";
193    }
194
195
196    static class Entry<V> implements Cacheable.Entry<V>
197    {
198        final Date timestamp;
199        Date       lastAccess;
200        V          value;
201        int hits;
202        
203        public Entry(V value)
204        {
205            this.timestamp = new Date();
206            this.lastAccess = new Date();
207            this.value = value;
208            this.hits = 0;
209        }
210        
211        @Override
212        public final Date getTimestamp()
213        {
214            return timestamp;
215        }
216        
217        @Override
218        public Date getLastAccess()
219        {
220            return this.lastAccess;
221        }
222        
223        @Override
224        public final V getValue()
225        {
226            this.lastAccess = new Date();
227            hits++;
228            return value;
229        }
230        
231        @Override
232        public int hits()
233        {
234            return this.hits;
235        }
236        
237    }
238    
239}