/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2006-2008, Open Source Geospatial Foundation (OSGeo) * * 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; * version 2.1 of the License. * * 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. */ package org.geotools.util; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; /** * A {@link Map} with a fixed maximum size which removes the <cite>least recently used</cite> (LRU) * entry if an entry is added when full. This class implements a simple technique for LRU pooling * of objects. * <p> * This class is <strong>not</strong> thread-safe. Synchronizations (if wanted) are user's * reponsability. * * @param <K> The type of keys in the map. * @param <V> The type of values in the map. * * * @source $URL$ * @version $Id$ * @author Simone Giannecchini * @author Martin Desruisseaux * @since 2.3 */ public final class LRULinkedHashMap<K,V> extends LinkedHashMap<K,V> { /** * Serial number for cross-version compatibility. */ private static final long serialVersionUID = -6668885347230182669L; /** * The default load factor copied from {@link HashMap} package-privated field. */ private static final double DEFAULT_LOAD_FACTOR = 0.75; /** * Default maximum size (not to be confused with capacity). */ private static final int DEFAULT_MAXIMUM_SIZE = 100; /** * Maximum number of entries for this LRU map. */ private int maximumSize; /** * Constructs a {@code LRULinkedHashMap} with default initial capacity, maximum size * and load factor. */ public LRULinkedHashMap() { super(); maximumSize = DEFAULT_MAXIMUM_SIZE; } /** * Constructs a {@code LRULinkedHashMap} with default maximum size and load factor. * * @param initialCapacity The initial capacity. */ public LRULinkedHashMap(final int initialCapacity) { super(initialCapacity); maximumSize = DEFAULT_MAXIMUM_SIZE; } /** * Constructs a {@code LRULinkedHashMap} with default maximum size. * * @param initialCapacity The initial capacity. * @param loadFactor The load factor. */ public LRULinkedHashMap(final int initialCapacity, final float loadFactor) { super(initialCapacity, loadFactor); maximumSize = DEFAULT_MAXIMUM_SIZE; } /** * Constructs a {@code LRULinkedHashMap} with default maximum size. * * @param initialCapacity The initial capacity. * @param loadFactor The load factor. * @param accessOrder The ordering mode: {@code true} for access-order, * {@code false} for insertion-order. */ public LRULinkedHashMap(final int initialCapacity, final float loadFactor, final boolean accessOrder) { super(initialCapacity, loadFactor, accessOrder); maximumSize = DEFAULT_MAXIMUM_SIZE; } /** * Constructs a {@code LRULinkedHashMap} with the specified maximum size. * * @param initialCapacity The initial capacity. * @param loadFactor The load factor. * @param accessOrder The ordering mode: {@code true} for access-order, * {@code false} for insertion-order. * @param maximumSize Maximum number of entries for this LRU map. */ public LRULinkedHashMap(final int initialCapacity, final float loadFactor, final boolean accessOrder, final int maximumSize) { super(initialCapacity, loadFactor, accessOrder); this.maximumSize = maximumSize; checkMaximumSize(maximumSize); } /** * For private usage by static convenience constructors only. The {@code maximumSize} must * be checked before this construcor is invoked, because it is used in the calculation of * the value given to {@code super} constructor call. */ private LRULinkedHashMap(final boolean accessOrder, final int maximumSize) { super((int) Math.ceil(maximumSize / DEFAULT_LOAD_FACTOR), (float)DEFAULT_LOAD_FACTOR, accessOrder); this.maximumSize = maximumSize; } /** * Constructs a {@code LRULinkedHashMap} with all entries from the specified map. * The {@linkplain #getMaximumSize maximum size} is set to the given * {@linkplain Map#size map size}. * * @param map The map whose mappings are to be placed in this map. * * @since 2.5 */ public LRULinkedHashMap(final Map<K,V> map) { super(map); maximumSize = map.size(); } /** * Constructs a {@code LRULinkedHashMap} with all entries from the specified map * and maximum number of entries. * * @param map The map whose mappings are to be placed in this map. * @param maximumSize Maximum number of entries for this LRU map. * * @since 2.5 */ public LRULinkedHashMap(final Map<K,V> map, final int maximumSize) { super(map); this.maximumSize = maximumSize; checkMaximumSize(maximumSize); removeExtraEntries(); } /** * Ensures that the given size is strictly positive. */ private static void checkMaximumSize(final int maximumSize) throws IllegalArgumentException { if (maximumSize <= 0) { throw new IllegalArgumentException(Errors.format( ErrorKeys.NOT_GREATER_THAN_ZERO_$1, maximumSize)); } } /** * If there is more entries than the maximum amount, remove extra entries. * <p> * <b>Note:</b> Invoking {@code removeExtraEntries()} after adding all entries in the * {@link #LRULinkedHashMap(Map,int)} constructor is less efficient than just iterating over * the {@link #maximumSize} first entries at construction time, but super-class constructor * is more efficient for maps with less than {@code maximumSize}. We assume that this is the * most typical case. */ private void removeExtraEntries() { if (size() > maximumSize) { final Iterator<Map.Entry<K,V>> it = entrySet().iterator(); for (int c=0; c<maximumSize; c++) { it.next(); } while (it.hasNext()) { it.remove(); } } } /** * Creates a map for the most recently accessed entries. This convenience constructor * uses an initial capacity big enough for holding the given maximum number of entries, * so it is not appropriate if {@code maximumSize} is very large. * * @param <K> The type of keys in the map. * @param <V> The type of values in the map. * * @param maximumSize Maximum number of entries for this LRU map. * @return A map holding only the {@code maximumSize} most recently accessed entries. * * @since 2.5 */ public static <K,V> LRULinkedHashMap<K,V> createForRecentAccess(final int maximumSize) { checkMaximumSize(maximumSize); return new LRULinkedHashMap<K,V>(true, maximumSize); } /** * Creates a map for the most recently inserted entries. This convenience constructor * uses an initial capacity big enough for holding the given maximum number of entries, * so it is not appropriate if {@code maximumSize} is very large. * * @param <K> The type of keys in the map. * @param <V> The type of values in the map. * * @param maximumSize Maximum number of entries for this LRU map. * @return A map holding only the {@code maximumSize} most recently inserted entries. * * @since 2.5 */ public static <K,V> LRULinkedHashMap<K,V> createForRecentInserts(final int maximumSize) { checkMaximumSize(maximumSize); return new LRULinkedHashMap<K,V>(false, maximumSize); } /** * Returns the maximal {@linkplain #size size} allowed for this map. * This is the value given to constructors. * * @return The maximal size allowed for this map. * * @since 2.5 */ public int getMaximumSize() { return maximumSize; } /** * Sets the maximal {@linkplain #size size} allowed for this map. If the given size * is smaller than the previous one, eldest entries will be immediately removed. * * @param max The new maximal size. * @throws IllegalArgumentException if the given size is negative. * * @since 2.5 */ public void setMaximumSize(final int max) { checkMaximumSize(max); maximumSize = max; removeExtraEntries(); } /** * Returns {@code true} if this map should remove its eldest entry. The default implementation * returns {@code true} if the {@linkplain #size number of entries} in this map has reached the * maximum number of entries specified at construction time. * * @param eldest The least recently inserted entry in the map, or if this is an access-ordered * map, the least recently accessed entry. This is the entry that will be removed it this * method returns {@code true}. * @return {@code true} if the eldest entry should be removed from the map; * {@code false} if it should be retained. */ @Override protected boolean removeEldestEntry(final Map.Entry<K,V> eldest) { return size() > maximumSize; } }