001/* 002 * #%L 003 * Netarchivesuite - wayback 004 * %% 005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library, 006 * the National Library of France and the Austrian National Library. 007 * %% 008 * This program is free software: you can redistribute it and/or modify 009 * it under the terms of the GNU Lesser General Public License as 010 * published by the Free Software Foundation, either version 2.1 of the 011 * License, or (at your option) any later version. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Lesser Public License for more details. 017 * 018 * You should have received a copy of the GNU General Lesser Public 019 * License along with this program. If not, see 020 * <http://www.gnu.org/licenses/lgpl-2.1.html>. 021 * #L% 022 */ 023package dk.netarkivet.wayback; 024 025import java.io.File; 026import java.util.LinkedHashMap; 027import java.util.Map; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031 032import dk.netarkivet.common.exceptions.ArgumentNotValid; 033import dk.netarkivet.common.utils.Settings; 034 035/** 036 * An LRU cache, based on <code>LinkedHashMap</code>. 037 * <p> 038 * <p> 039 * This cache has a fixed maximum number of elements (<code>cacheSize</code>). If the cache is full and another entry is 040 * added, the LRU (least recently used) entry is dropped. 041 * <p> 042 * <p> 043 * This class is thread-safe. All methods of this class are synchronized. 044 * <p> 045 * <p> 046 * Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br> 047 * Multi-licensed: EPL / LGPL / GPL / AL / BSD. 048 * <p> 049 * Modified slightly to fit the use of a wayback file cache. 050 */ 051public class LRUCache { 052 /** The hashtable loadfactor used here. */ 053 private static final float hashTableLoadFactor = 0.75f; 054 /** The instance of this class. */ 055 private static LRUCache instance = null; 056 /** The map containing pointers to the cache itself. */ 057 private LinkedHashMap<String, File> map; 058 /** The size of the cache. */ 059 private int cacheSize; 060 /** The cache containing the files. */ 061 private File cacheDir; 062 063 /** Logger. */ 064 private Log logger = LogFactory.getLog(getClass().getName()); 065 066 /** 067 * Creates a new LRU cache. Using filename as the key, and the cached file as the value. 068 * 069 * @param dir The directory where the file is stored. 070 * @param cacheSize the maximum number of entries that will be kept in this cache. 071 */ 072 public LRUCache(File dir, int cacheSize) { 073 // Validate args 074 ArgumentNotValid.checkPositive(cacheSize, "int cacheSize"); 075 ArgumentNotValid.checkNotNull(dir, "File dir"); 076 dir.mkdirs(); 077 ArgumentNotValid.checkTrue(dir.exists(), "Cachedir '" + dir.getAbsolutePath() + "' does not exist"); 078 079 this.cacheSize = cacheSize; 080 this.cacheDir = dir; 081 082 int hashTableCapacity = (int) Math.ceil(cacheSize / hashTableLoadFactor) + 1; 083 map = new LinkedHashMap<String, File>(hashTableCapacity, hashTableLoadFactor, true) { 084 // (an anonymous inner class) 085 private static final long serialVersionUID = 1; 086 087 @Override 088 protected boolean removeEldestEntry(Map.Entry<String, File> eldest) { 089 boolean removeEldest = size() > LRUCache.this.cacheSize; 090 if (removeEldest) { 091 logger.info("Deleting file '" + eldest.getValue().getAbsolutePath() + "' from cache."); 092 boolean deleted = eldest.getValue().delete(); 093 if (!deleted) { 094 logger.warn("Unable to deleted LRU file from cache: " + eldest.getValue()); 095 } 096 } 097 return removeEldest; 098 } 099 }; 100 101 // fill up the map with the contents in cachedir 102 // if the contents in cachedir exceeds the given cachesize, 103 // change the size of the cache 104 String[] cachedirFiles = cacheDir.list(); 105 logger.info("Initializing the cache with the contents of the cachedir '" + cacheDir.getAbsolutePath() + "'"); 106 if (cachedirFiles.length > this.cacheSize) { 107 logger.warn("Changed the cachesize from " + cacheSize + " to " + cachedirFiles.length); 108 this.cacheSize = cachedirFiles.length; 109 } 110 for (String cachefile : cachedirFiles) { 111 map.put(cachefile, new File(cacheDir, cachefile)); 112 } 113 logger.info("The contents of the cache is now " + map.size() + " files"); 114 } 115 116 /** 117 * Constructor, where the arguments for the primary constructor is read from settings. 118 */ 119 public LRUCache() { 120 this(new File(Settings.get(WaybackSettings.WAYBACK_RESOURCESTORE_CACHE_DIR)), Settings 121 .getInt(WaybackSettings.WAYBACK_RESOURCESTORE_CACHE_MAXFILES)); 122 } 123 124 /** 125 * @return instance of our Cache 126 */ 127 public static synchronized LRUCache getInstance() { 128 if (instance == null) { 129 instance = new LRUCache(); 130 } 131 return instance; 132 } 133 134 /** 135 * Retrieves an entry from the cache.<br> 136 * The retrieved entry becomes the MRU (most recently used) entry. 137 * 138 * @param key the key whose associated value is to be returned. 139 * @return the value associated to this key, or null if no value with this key exists in the cache. 140 */ 141 public synchronized File get(String key) { 142 return map.get(key); 143 } 144 145 /** 146 * Adds an entry to this cache. The new entry becomes the MRU (most recently used) entry. If an entry with the 147 * specified key already exists in the cache, it is replaced by the new entry. If the cache is full, the LRU (least 148 * recently used) entry is removed from the cache. 149 * 150 * @param key the key with which the specified value is to be associated. 151 * @param value a value to be associated with the specified key. 152 */ 153 public synchronized void put(String key, File value) { 154 map.put(key, value); 155 } 156 157 /** 158 * Clears the cache. 159 */ 160 public synchronized void clear() { 161 map.clear(); 162 } 163 164 /** 165 * Returns the number of used entries in the cache. 166 * 167 * @return the number of entries currently in the cache. 168 */ 169 public synchronized int usedEntries() { 170 return map.size(); 171 } 172 173 /** 174 * @return the cacheDir 175 */ 176 public File getCacheDir() { 177 return cacheDir; 178 } 179}