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}