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&lt;K, V&gt; 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}