All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opencms.cache.CmsLruCache Maven / Gradle / Ivy

Go to download

OpenCms is an enterprise-ready, easy to use website content management system based on Java and XML technology. Offering a complete set of features, OpenCms helps content managers worldwide to create and maintain beautiful websites fast and efficiently.

There is a newer version: 17.0
Show newest version
/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software GmbH & Co. KG, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.cache;

import org.opencms.main.CmsLog;

import org.apache.commons.logging.Log;

/**
 * Implements an LRU (last recently used) cache.

* * The idea of this cache is to separate the caching policy from the data structure * where the cached objects are stored. The advantage of doing so is, that the CmsFlexLruCache * can identify the last-recently-used object in O(1), whereas you would need at least * O(n) to traverse the data structure that stores the cached objects. Second, you can * easily use the CmsFlexLruCache to get an LRU cache, no matter what data structure is used to * store your objects. *

* The cache policy is affected by the "costs" of the objects being cached. Valuable cache costs * might be the byte size of the cached objects for example. *

* To add/remove cached objects from the data structure that stores them, the objects have to * implement the methods defined in the interface I_CmsLruCacheObject to be notified when they * are added/removed from the CmsFlexLruCache.

* * @see org.opencms.cache.I_CmsLruCacheObject * * @since 6.0.0 */ public class CmsLruCache extends java.lang.Object { /** The log object for this class. */ private static final Log LOG = CmsLog.getLog(CmsLruCache.class); /** The average sum of costs the cached objects. */ private long m_avgCacheCosts; /** The head of the list of double linked LRU cache objects. */ private I_CmsLruCacheObject m_listHead; /** The tail of the list of double linked LRU cache objects. */ private I_CmsLruCacheObject m_listTail; /** The maximum sum of costs the cached objects might reach. */ private long m_maxCacheCosts; /** The maximum costs of cacheable objects. */ private int m_maxObjectCosts; /** The costs of all cached objects. */ private int m_objectCosts; /** The sum of all cached objects. */ private int m_objectCount; /** * The constructor with all options.

* * @param theMaxCacheCosts the maximum cache costs of all cached objects * @param theAvgCacheCosts the average cache costs of all cached objects * @param theMaxObjectCosts the maximum allowed cache costs per object. Set theMaxObjectCosts to -1 if you don't want to limit the max. allowed cache costs per object */ public CmsLruCache(long theMaxCacheCosts, long theAvgCacheCosts, int theMaxObjectCosts) { m_maxCacheCosts = theMaxCacheCosts; m_avgCacheCosts = theAvgCacheCosts; m_maxObjectCosts = theMaxObjectCosts; } /** * Adds a new object to this cache.

* * If add the same object more than once, * the object is touched instead.

* * @param theCacheObject the object being added to the cache * @return true if the object was added to the cache, false if the object was denied because its cache costs were higher than the allowed max. cache costs per object */ public synchronized boolean add(I_CmsLruCacheObject theCacheObject) { if (theCacheObject == null) { // null can't be added or touched in the cache return false; } // only objects with cache costs < the max. allowed object cache costs can be cached! if ((m_maxObjectCosts != -1) && (theCacheObject.getLruCacheCosts() > m_maxObjectCosts)) { if (LOG.isInfoEnabled()) { LOG.info( Messages.get().getBundle().key( Messages.LOG_CACHE_COSTS_TOO_HIGH_2, new Integer(theCacheObject.getLruCacheCosts()), new Integer(m_maxObjectCosts))); } return false; } if (!isCached(theCacheObject)) { // add the object to the list of all cached objects in the cache addHead(theCacheObject); } else { touch(theCacheObject); } // check if the cache has to trash the last-recently-used objects before adding a new object if (m_objectCosts > m_maxCacheCosts) { gc(); } return true; } /** * Removes all cached objects in this cache.

*/ public synchronized void clear() { // remove all objects from the linked list from the tail to the head: I_CmsLruCacheObject currentObject = m_listTail; while (currentObject != null) { currentObject = currentObject.getNextLruObject(); removeTail(); } // reset the data structure m_objectCosts = 0; m_objectCount = 0; m_listHead = null; m_listTail = null; } /** * Returns the average costs of all cached objects.

* * @return the average costs of all cached objects */ public long getAvgCacheCosts() { return m_avgCacheCosts; } /** * Returns the max costs of all cached objects.

* * @return the max costs of all cached objects */ public long getMaxCacheCosts() { return m_maxCacheCosts; } /** * Returns the max allowed costs per cached object.

* * @return the max allowed costs per cached object */ public int getMaxObjectCosts() { return m_maxObjectCosts; } /** * Returns the current costs of all cached objects.

* * @return the current costs of all cached objects */ public int getObjectCosts() { return m_objectCosts; } /** * Removes an object from the list of all cached objects in this cache, * no matter what position it has inside the list.

* * @param theCacheObject the object being removed from the list of all cached objects * @return a reference to the object that was removed */ public synchronized I_CmsLruCacheObject remove(I_CmsLruCacheObject theCacheObject) { if (!isCached(theCacheObject)) { // theCacheObject is null or not inside the cache return null; } // set the list pointers correct boolean nextNull = (theCacheObject.getNextLruObject() == null); boolean prevNull = (theCacheObject.getPreviousLruObject() == null); if (prevNull && nextNull) { m_listHead = null; m_listTail = null; } else if (nextNull) { // remove the object from the head pos. I_CmsLruCacheObject newHead = theCacheObject.getPreviousLruObject(); newHead.setNextLruObject(null); m_listHead = newHead; } else if (prevNull) { // remove the object from the tail pos. I_CmsLruCacheObject newTail = theCacheObject.getNextLruObject(); newTail.setPreviousLruObject(null); m_listTail = newTail; } else { // remove the object from within the list theCacheObject.getPreviousLruObject().setNextLruObject(theCacheObject.getNextLruObject()); theCacheObject.getNextLruObject().setPreviousLruObject(theCacheObject.getPreviousLruObject()); } // update cache stats. and notify the cached object decreaseCache(theCacheObject); return theCacheObject; } /** * Returns the count of all cached objects.

* * @return the count of all cached objects */ public int size() { return m_objectCount; } /** * Returns a string representing the current state of the cache.

* * @return a string representing the current state of the cache */ @Override public String toString() { StringBuffer buf = new StringBuffer(); buf.append("max. costs: " + m_maxCacheCosts).append(", "); buf.append("avg. costs: " + m_avgCacheCosts).append(", "); buf.append("max. costs/object: " + m_maxObjectCosts).append(", "); buf.append("costs: " + m_objectCosts).append(", "); buf.append("count: " + m_objectCount); return buf.toString(); } /** * Touch an existing object in this cache, in the sense that it's "last-recently-used" state * is updated.

* * @param theCacheObject the object being touched * @return true if an object was found and touched */ public synchronized boolean touch(I_CmsLruCacheObject theCacheObject) { if (!isCached(theCacheObject)) { return false; } // only objects with cache costs < the max. allowed object cache costs can be cached! if ((m_maxObjectCosts != -1) && (theCacheObject.getLruCacheCosts() > m_maxObjectCosts)) { if (LOG.isInfoEnabled()) { LOG.info( Messages.get().getBundle().key( Messages.LOG_CACHE_COSTS_TOO_HIGH_2, new Integer(theCacheObject.getLruCacheCosts()), new Integer(m_maxObjectCosts))); } remove(theCacheObject); return false; } // set the list pointers correct I_CmsLruCacheObject nextObj = theCacheObject.getNextLruObject(); if (nextObj == null) { // case 1: the object is already at the head pos. return true; } I_CmsLruCacheObject prevObj = theCacheObject.getPreviousLruObject(); if (prevObj == null) { // case 2: the object at the tail pos., remove it from the tail to put it to the front as the new head I_CmsLruCacheObject newTail = nextObj; newTail.setPreviousLruObject(null); m_listTail = newTail; } else { // case 3: the object is somewhere within the list, remove it to put it the front as the new head prevObj.setNextLruObject(nextObj); nextObj.setPreviousLruObject(prevObj); } // set the touched object as the new head in the linked list: I_CmsLruCacheObject oldHead = m_listHead; if (oldHead != null) { oldHead.setNextLruObject(theCacheObject); theCacheObject.setNextLruObject(null); theCacheObject.setPreviousLruObject(oldHead); } m_listHead = theCacheObject; return true; } /** * Adds a cache object as the new haed to the list of all cached objects in this cache.

* * @param theCacheObject the object being added as the new head to the list of all cached objects */ private void addHead(I_CmsLruCacheObject theCacheObject) { // set the list pointers correct if (m_objectCount > 0) { // there is at least 1 object already in the list I_CmsLruCacheObject oldHead = m_listHead; oldHead.setNextLruObject(theCacheObject); theCacheObject.setPreviousLruObject(oldHead); m_listHead = theCacheObject; } else { // it is the first object to be added to the list m_listTail = theCacheObject; m_listHead = theCacheObject; theCacheObject.setPreviousLruObject(null); } theCacheObject.setNextLruObject(null); // update cache stats. and notify the cached object increaseCache(theCacheObject); } /** * Decrease this caches statistics * and notify the cached object that it was removed from this cache.

* * @param theCacheObject the object being notified that it was removed from the cache */ private void decreaseCache(I_CmsLruCacheObject theCacheObject) { // notify the object that it was now removed from the cache //theCacheObject.notify(); theCacheObject.removeFromLruCache(); // set the list pointers to null theCacheObject.setNextLruObject(null); theCacheObject.setPreviousLruObject(null); // update the cache stats. m_objectCosts -= theCacheObject.getLruCacheCosts(); m_objectCount--; } /** * Removes the last recently used objects from the list of all cached objects as long * as the costs of all cached objects are higher than the allowed avg. costs of the cache.

*/ private void gc() { I_CmsLruCacheObject currentObject = m_listTail; while (currentObject != null) { if (m_objectCosts < m_avgCacheCosts) { break; } currentObject = currentObject.getNextLruObject(); removeTail(); } } /** * Increase this caches statistics * and notify the cached object that it was added to this cache.

* * @param theCacheObject the object being notified that it was added to the cache */ private void increaseCache(I_CmsLruCacheObject theCacheObject) { // notify the object that it was now added to the cache //theCacheObject.notify(); theCacheObject.addToLruCache(); // update the cache stats. m_objectCosts += theCacheObject.getLruCacheCosts(); m_objectCount++; } /** * Test if a given object resides inside the cache.

* * @param theCacheObject the object to test * @return true if the object is inside the cache, false otherwise */ private boolean isCached(I_CmsLruCacheObject theCacheObject) { if ((theCacheObject == null) || (m_objectCount == 0)) { // the cache is empty or the object is null (which is never cached) return false; } I_CmsLruCacheObject nextObj = theCacheObject.getNextLruObject(); I_CmsLruCacheObject prevObj = theCacheObject.getPreviousLruObject(); if ((nextObj != null) || (prevObj != null)) { // the object has either a predecessor or successor in the linked // list of all cached objects, so it is inside the cache return true; } // both nextObj and preObj are null if ((m_objectCount == 1) && (m_listHead != null) && (m_listTail != null) && m_listHead.equals(theCacheObject) && m_listTail.equals(theCacheObject)) { // the object is the one and only object in the cache return true; } return false; } /** * Removes the tailing object from the list of all cached objects.

*/ private synchronized void removeTail() { I_CmsLruCacheObject oldTail = m_listTail; if (oldTail != null) { I_CmsLruCacheObject newTail = oldTail.getNextLruObject(); // set the list pointers correct if (newTail != null) { // there are still objects remaining in the list newTail.setPreviousLruObject(null); m_listTail = newTail; } else { // we removed the last object from the list m_listTail = null; m_listHead = null; } // update cache stats. and notify the cached object decreaseCache(oldTail); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy