Package org.apache.jcs.engine.memory.mru

Source Code of org.apache.jcs.engine.memory.mru.MRUMemoryCache

package org.apache.jcs.engine.memory.mru;

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.jcs.engine.CacheConstants;
import org.apache.jcs.engine.behavior.ICacheElement;
import org.apache.jcs.engine.control.CompositeCache;
import org.apache.jcs.engine.control.group.GroupAttrName;
import org.apache.jcs.engine.control.group.GroupId;
import org.apache.jcs.engine.memory.AbstractMemoryCache;
import org.apache.jcs.engine.stats.StatElement;
import org.apache.jcs.engine.stats.Stats;
import org.apache.jcs.engine.stats.behavior.IStatElement;
import org.apache.jcs.engine.stats.behavior.IStats;

/**
* A SLOW reference management system. The most recently used items move to the
* front of the list and get spooled to disk if the cache hub is configured to
* use a disk cache.
* <p>
* This class is mainly for testing the hub. It also shows that use of the
* Collection LinkedList is far slower than JCS' own double linked list.
*/
public class MRUMemoryCache
    extends AbstractMemoryCache
{
    private static final long serialVersionUID = 5013101678192336129L;

    private final static Log log = LogFactory.getLog( MRUMemoryCache.class );

    private int hitCnt = 0;

    private int missCnt = 0;

    private int putCnt = 0;

    /**
     * Object to lock on the Field
     */
    private int[] lockMe = new int[0];

    /**
     * MRU list.
     */
    private LinkedList mrulist = new LinkedList();

    /**
     * For post reflection creation initialization
     * @param hub
     */
    public synchronized void initialize( CompositeCache hub )
    {
        super.initialize( hub );
        log.info( "Initialized MRUMemoryCache for " + cacheName );
    }

    /**
     * Puts an item to the cache.
     * @param ce
     * @exception IOException
     */
    public void update( ICacheElement ce )
        throws IOException
    {
        putCnt++;

        Serializable key = ce.getKey();
        ce.getElementAttributes().setLastAccessTimeNow();

        // need a more fine grained locking here
        boolean replace = false;
        if ( map.containsKey( key ) )
        {
            replace = true;
        }
        synchronized ( lockMe )
        {
            map.put( key, ce );
            if ( replace )
            {
                // the slowest method I've ever seen
                mrulist.remove( key );
            }
            mrulist.addFirst( key );
        }

        // save a microsecond on the second call.
        int size = map.size();
        // need to spool at a certain percentage synchronously
        if ( size < this.cattr.getMaxObjects() )
        {
            return;
        }
        // SPOOL LAST -- need to make this a grouping in a queue

        if ( log.isDebugEnabled() )
        {
            log.debug( "In RAM overflow" );
        }

        // write the last item to disk.
        try
        {
            // PUSH more than 1 TO DISK TO MINIMIZE THE TYPICAL spool at each
            // put.
            int chunkSizeCorrected = Math.min( size, chunkSize );

            if ( log.isDebugEnabled() )
            {
                log.debug( "update: About to spool to disk cache, map.size() = " + size
                    + ", this.cattr.getMaxObjects() = " + this.cattr.getMaxObjects() + ", chunkSizeCorrected = "
                    + chunkSizeCorrected );
            }

            // The spool will put them in a disk event queue, so there is no
            // need to pre-queue the queuing. This would be a bit wasteful
            // and wouldn't save much time in this synchronous call.
            for ( int i = 0; i < chunkSizeCorrected; i++ )
            {
                // remove the last
                spoolLastElement();
            }

            if ( log.isDebugEnabled() )
            {
                log.debug( "update: After spool,  map.size() = " + size + ", this.cattr.getMaxObjects() = "
                    + this.cattr.getMaxObjects() + ", chunkSizeCorrected = " + chunkSizeCorrected );
            }

        }
        catch ( Exception ex )
        {
            // impossible case.
            log.error( "Problem updating MRU.", ex );
            throw new IllegalStateException( ex.getMessage() );
        }
    }

    /**
     * This removes the last elemement in the list.
     * <p>
     * @return ICacheElement if there was a last element, else null.
     */
    protected ICacheElement spoolLastElement()
    {
        ICacheElement toSpool = null;

        // need a more fine grained locking here
        synchronized ( lockMe )
        {
            Serializable last = (Serializable) mrulist.removeLast();
            if ( last != null )
            {
                toSpool = (ICacheElement) map.get( last );
                map.remove( last );
            }
        }
        // Might want to rename this "overflow" incase the hub
        // wants to do something else.
        if ( toSpool != null )
        {
            cache.spoolToDisk( toSpool );
        }

        return toSpool;
    }

    /**
     * This instructs the memory cache to remove the <i>numberToFree</i>
     * according to its eviction policy. For example, the LRUMemoryCache will
     * remove the <i>numberToFree</i> least recently used items. These will be
     * spooled to disk if a disk auxiliary is available.
     * <p>
     * @param numberToFree
     * @return the number that were removed. if you ask to free 5, but there are
     *         only 3, you will get 3.
     * @throws IOException
     */
    public int freeElements( int numberToFree )
        throws IOException
    {
        int freed = 0;
        for ( ; freed < numberToFree; freed++ )
        {
            ICacheElement element = spoolLastElement();
            if ( element == null )
            {
                break;
            }
        }
        return freed;
    }

    /**
     * Get an item from the cache without affecting its last access time or
     * position.
     * @return Element matching key if found, or null
     * @param key
     *            Identifies item to find
     * @exception IOException
     */
    public ICacheElement getQuiet( Serializable key )
        throws IOException
    {
        ICacheElement ce = null;

        try
        {
            ce = (ICacheElement) map.get( key );
            if ( ce != null )
            {
                if ( log.isDebugEnabled() )
                {
                    log.debug( cacheName + ": MRUMemoryCache quiet hit for " + key );
                }
            }
            else
            {
                log.debug( cacheName + ": MRUMemoryCache quiet miss for " + key );
            }
        }
        catch ( Exception e )
        {
            log.error( "Problem getting quietly from MRU.", e );
        }

        return ce;
    }

    /**
     * Gets an item out of the map. If it finds an item, it is removed from the
     * list and then added to the first position in the linked list.
     * @return
     * @param key
     * @exception IOException
     */
    public ICacheElement get( Serializable key )
        throws IOException
    {
        ICacheElement ce = null;
        boolean found = false;

        try
        {
            if ( log.isDebugEnabled() )
            {
                log.debug( "get> key=" + key );
                log.debug( "get> key=" + key.toString() );
            }

            ce = (ICacheElement) map.get( key );
            if ( log.isDebugEnabled() )
            {
                log.debug( "ce =" + ce );
            }

            if ( ce != null )
            {
                found = true;
                ce.getElementAttributes().setLastAccessTimeNow();
                hitCnt++;
                if ( log.isDebugEnabled() )
                {
                    log.debug( cacheName + " -- RAM-HIT for " + key );
                }
            }
        }
        catch ( Exception e )
        {
            log.error( "Problem getting element.", e );
        }

        try
        {
            if ( !found )
            {
                // Item not found in cache.
                missCnt++;
                if ( log.isDebugEnabled() )
                {
                    log.debug( cacheName + " -- MISS for " + key );
                }
                return null;
            }
        }
        catch ( Exception e )
        {
            log.error( "Error handling miss", e );
            return null;
        }

        try
        {
            synchronized ( lockMe )
            {
                mrulist.remove( ce.getKey() );
                mrulist.addFirst( ce.getKey() );
            }
        }
        catch ( Exception e )
        {
            log.error( "Error making first", e );
            return null;
        }

        return ce;
    }

    /**
     * Removes an item from the cache.
     * @return
     * @param key
     * @exception IOException
     */
    public boolean remove( Serializable key )
        throws IOException
    {
        if ( log.isDebugEnabled() )
        {
            log.debug( "remove> key=" + key );
        }

        boolean removed = false;

        // handle partial removal
        if ( key instanceof String && key.toString().endsWith( CacheConstants.NAME_COMPONENT_DELIMITER ) )
        {
            // remove all keys of the same name hierarchy.
            synchronized ( lockMe )
            {
                for ( Iterator itr = map.entrySet().iterator(); itr.hasNext(); )
                {
                    Map.Entry entry = (Map.Entry) itr.next();
                    Object k = entry.getKey();
                    if ( k instanceof String && k.toString().startsWith( key.toString() ) )
                    {
                        itr.remove();
                        Serializable keyR = (Serializable) entry.getKey();
                        // map.remove( keyR );
                        mrulist.remove( keyR );
                        removed = true;
                    }
                }
            }
        }
        else if ( key instanceof GroupId )
        {
            // remove all keys of the same name hierarchy.
            synchronized ( lockMe )
            {
                for ( Iterator itr = map.entrySet().iterator(); itr.hasNext(); )
                {
                    Map.Entry entry = (Map.Entry) itr.next();
                    Object k = entry.getKey();

                    if ( k instanceof GroupAttrName && ( (GroupAttrName) k ).groupId.equals( key ) )
                    {
                        itr.remove();
                        mrulist.remove( k );
                        removed = true;
                    }
                }
            }
        }
        else
        {
            // remove single item.
            if ( map.containsKey( key ) )
            {
                synchronized ( lockMe )
                {
                    map.remove( key );
                    mrulist.remove( key );
                }
                removed = true;
            }
        }
        // end else not hierarchical removal
        return removed;
    }

    /**
     * Get an Array of the keys for all elements in the memory cache
     * @return Object[]
     */
    public Object[] getKeyArray()
    {
        // need to lock to map here?
        synchronized ( lockMe )
        {
            return map.keySet().toArray();
        }
    }

    /**
     * Dump the cache map for debugging.
     */
    public void dumpMap()
    {
        log.debug( "dumpingMap" );
        for ( Iterator itr = map.entrySet().iterator(); itr.hasNext(); )
        {
            // for ( Iterator itr = memCache.getIterator(); itr.hasNext();) {
            Map.Entry e = (Map.Entry) itr.next();
            ICacheElement ce = (ICacheElement) e.getValue();
            log.debug( "dumpMap> key=" + e.getKey() + ", val=" + ce.getVal() );
        }
    }

    /**
     * Dump the cache entries from first to list for debugging.
     */
    public void dumpCacheEntries()
    {
        log.debug( "dumpingCacheEntries" );
        ListIterator li = mrulist.listIterator();
        while ( li.hasNext() )
        {
            Serializable key = (Serializable) li.next();
            log.debug( "dumpCacheEntries> key=" + key + ", val=" + ( (ICacheElement) map.get( key ) ).getVal() );
        }
    }

    /*
     * (non-Javadoc)
     * @see org.apache.jcs.engine.memory.MemoryCache#getStatistics()
     */
    public IStats getStatistics()
    {
        IStats stats = new Stats();
        stats.setTypeName( "MRU Memory Cache" );

        ArrayList elems = new ArrayList();

        IStatElement se = null;

        se = new StatElement();
        se.setName( "List Size" );
        se.setData( "" + mrulist.size() );
        elems.add( se );

        se = new StatElement();
        se.setName( "Map Size" );
        se.setData( "" + map.size() );
        elems.add( se );

        se = new StatElement();
        se.setName( "Put Count" );
        se.setData( "" + putCnt );
        elems.add( se );

        se = new StatElement();
        se.setName( "Hit Count" );
        se.setData( "" + hitCnt );
        elems.add( se );

        se = new StatElement();
        se.setName( "Miss Count" );
        se.setData( "" + missCnt );
        elems.add( se );

        // get an array and put them in the Stats object
        IStatElement[] ses = (IStatElement[]) elems.toArray( new StatElement[0] );
        stats.setStatElements( ses );

        return stats;
    }
}
TOP

Related Classes of org.apache.jcs.engine.memory.mru.MRUMemoryCache

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.