001package org.apache.commons.jcs.engine.memory.lru; 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.util.Collections; 024import java.util.Iterator; 025import java.util.LinkedHashSet; 026import java.util.Map; 027import java.util.Set; 028 029import org.apache.commons.jcs.engine.CacheConstants; 030import org.apache.commons.jcs.engine.behavior.ICacheElement; 031import org.apache.commons.jcs.engine.control.CompositeCache; 032import org.apache.commons.jcs.engine.control.group.GroupAttrName; 033import org.apache.commons.jcs.engine.memory.AbstractMemoryCache; 034import org.apache.commons.jcs.engine.memory.util.MemoryElementDescriptor; 035import org.apache.commons.jcs.engine.stats.behavior.IStats; 036import org.apache.commons.logging.Log; 037import org.apache.commons.logging.LogFactory; 038 039/** 040 * This is a test memory manager using the jdk1.4 LinkedHashMap. 041 */ 042public class LHMLRUMemoryCache<K, V> 043 extends AbstractMemoryCache<K, V> 044{ 045 /** The Logger. */ 046 private static final Log log = LogFactory.getLog( LRUMemoryCache.class ); 047 048 /** 049 * For post reflection creation initialization 050 * <p> 051 * @param hub 052 */ 053 @Override 054 public void initialize( CompositeCache<K, V> hub ) 055 { 056 super.initialize( hub ); 057 log.info( "initialized LHMLRUMemoryCache for " + getCacheName() ); 058 } 059 060 /** 061 * Returns a synchronized LHMSpooler 062 * <p> 063 * @return Collections.synchronizedMap( new LHMSpooler() ) 064 */ 065 @Override 066 public Map<K, MemoryElementDescriptor<K, V>> createMap() 067 { 068 return Collections.synchronizedMap( new LHMSpooler() ); 069 } 070 071 /** 072 * Puts an item to the cache. 073 * <p> 074 * @param ce Description of the Parameter 075 * @throws IOException 076 */ 077 @Override 078 public void update( ICacheElement<K, V> ce ) 079 throws IOException 080 { 081 putCnt.incrementAndGet(); 082 map.put( ce.getKey(), new MemoryElementDescriptor<K, V>(ce) ); 083 } 084 085 /** 086 * Get an item from the cache 087 * <p> 088 * @param key Identifies item to find 089 * @return ICacheElement<K, V> if found, else null 090 * @throws IOException 091 */ 092 @Override 093 public ICacheElement<K, V> get( K key ) 094 throws IOException 095 { 096 if ( log.isDebugEnabled() ) 097 { 098 log.debug( "getting item from cache " + getCacheName() + " for key " + key ); 099 } 100 101 MemoryElementDescriptor<K, V> me = map.get( key ); 102 103 if ( me != null ) 104 { 105 hitCnt.incrementAndGet(); 106 if ( log.isDebugEnabled() ) 107 { 108 log.debug( getCacheName() + ": LHMLRUMemoryCache hit for " + key ); 109 } 110 return me.getCacheElement(); 111 } 112 else 113 { 114 missCnt.incrementAndGet(); 115 if ( log.isDebugEnabled() ) 116 { 117 log.debug( getCacheName() + ": LHMLRUMemoryCache miss for " + key ); 118 } 119 } 120 121 return null; 122 } 123 124 /** 125 * Removes an item from the cache. This method handles hierarchical removal. If the key is a 126 * String and ends with the CacheConstants.NAME_COMPONENT_DELIMITER, then all items with keys 127 * starting with the argument String will be removed. 128 * <p> 129 * @param key 130 * @return true if removed 131 * @throws IOException 132 */ 133 @Override 134 public boolean remove( K key ) 135 throws IOException 136 { 137 if ( log.isDebugEnabled() ) 138 { 139 log.debug( "removing item for key: " + key ); 140 } 141 142 boolean removed = false; 143 144 // handle partial removal 145 if ( key instanceof String && ( (String) key ).endsWith( CacheConstants.NAME_COMPONENT_DELIMITER ) ) 146 { 147 // remove all keys of the same name hierarchy. 148 synchronized ( map ) 149 { 150 for (Iterator<Map.Entry<K, MemoryElementDescriptor<K, V>>> itr = map.entrySet().iterator(); itr.hasNext(); ) 151 { 152 Map.Entry<K, MemoryElementDescriptor<K, V>> entry = itr.next(); 153 K k = entry.getKey(); 154 155 if ( k instanceof String && ( (String) k ).startsWith( key.toString() ) ) 156 { 157 itr.remove(); 158 removed = true; 159 } 160 } 161 } 162 } 163 else if ( key instanceof GroupAttrName && ((GroupAttrName<?>)key).attrName == null ) 164 { 165 // remove all keys of the same name hierarchy. 166 synchronized ( map ) 167 { 168 for (Iterator<Map.Entry<K, MemoryElementDescriptor<K, V>>> itr = map.entrySet().iterator(); itr.hasNext(); ) 169 { 170 Map.Entry<K, MemoryElementDescriptor<K, V>> entry = itr.next(); 171 K k = entry.getKey(); 172 173 if ( k instanceof GroupAttrName && 174 ((GroupAttrName<?>)k).groupId.equals(((GroupAttrName<?>)key).groupId) ) 175 { 176 itr.remove(); 177 removed = true; 178 } 179 } 180 } 181 } 182 else 183 { 184 // remove single item. 185 MemoryElementDescriptor<K, V> me = map.remove( key ); 186 if ( me != null ) 187 { 188 removed = true; 189 } 190 } 191 192 return removed; 193 } 194 195 /** 196 * Get an Array of the keys for all elements in the memory cache 197 * <p> 198 * @return An Object[] 199 */ 200 @Override 201 public Set<K> getKeySet() 202 { 203 return new LinkedHashSet<K>(map.keySet()); 204 } 205 206 /** 207 * This returns semi-structured information on the memory cache, such as the size, put count, 208 * hit count, and miss count. 209 * <p> 210 * @return IStats 211 */ 212 @Override 213 public IStats getStatistics() 214 { 215 IStats stats = super.getStatistics(); 216 stats.setTypeName( "LHMLRU Memory Cache" ); 217 218 return stats; 219 } 220 221 // ---------------------------------------------------------- debug methods 222 223 /** 224 * Dump the cache entries from first to last for debugging. 225 */ 226 public void dumpCacheEntries() 227 { 228 dumpMap(); 229 } 230 231 /** 232 * This can't be implemented. 233 * <p> 234 * @param numberToFree 235 * @return 0 236 * @throws IOException 237 */ 238 @Override 239 public int freeElements( int numberToFree ) 240 throws IOException 241 { 242 // can't be implemented using the LHM 243 return 0; 244 } 245 246 // ---------------------------------------------------------- extended map 247 248 /** 249 * Implementation of removeEldestEntry in LinkedHashMap 250 */ 251 protected class LHMSpooler 252 extends java.util.LinkedHashMap<K, MemoryElementDescriptor<K, V>> 253 { 254 /** Don't change. */ 255 private static final long serialVersionUID = -1255907868906762484L; 256 257 /** 258 * Initialize to a small size--for now, 1/2 of max 3rd variable "true" indicates that it 259 * should be access and not time governed. This could be configurable. 260 */ 261 public LHMSpooler() 262 { 263 super( (int) ( getCacheAttributes().getMaxObjects() * .5 ), .75F, true ); 264 } 265 266 /** 267 * Remove eldest. Automatically called by LinkedHashMap. 268 * <p> 269 * @param eldest 270 * @return true if removed 271 */ 272 @SuppressWarnings("synthetic-access") 273 @Override 274 protected boolean removeEldestEntry( Map.Entry<K, MemoryElementDescriptor<K, V>> eldest ) 275 { 276 ICacheElement<K, V> element = eldest.getValue().getCacheElement(); 277 278 if ( size() <= getCacheAttributes().getMaxObjects() ) 279 { 280 return false; 281 } 282 else 283 { 284 285 if ( log.isDebugEnabled() ) 286 { 287 log.debug( "LHMLRU max size: " + getCacheAttributes().getMaxObjects() 288 + ". Spooling element, key: " + element.getKey() ); 289 } 290 291 waterfal( element ); 292 293 if ( log.isDebugEnabled() ) 294 { 295 log.debug( "LHMLRU size: " + map.size() ); 296 } 297 } 298 return true; 299 } 300 } 301}