/*
GNU GENERAL LICENSE
Copyright (C) 2006 The Lobo Project. Copyright (C) 2014 - 2017 Lobo Evolution
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
verion 3 of the License, or (at your option) any later version.
This program 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
General License for more details.
You should have received a copy of the GNU General Public
along with this program. If not, see <http://www.gnu.org/licenses/>.
Contact info: lobochief@users.sourceforge.net; ivan.difrancesco@yahoo.it
*/
package org.lobobrowser.http;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import org.lobobrowser.util.EventDispatch2;
import org.lobobrowser.util.RemovalDispatch;
import org.lobobrowser.util.RemovalEvent;
import org.lobobrowser.util.RemovalListener;
/**
* A cache with least-recently-used policy. Note that this class is not thread
* safe by itself.
*/
public class LRUCache implements java.io.Serializable {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 940427225784212823L;
/** The approx max size. */
private int approxMaxSize;
/** The cache map. */
private final Map<Object, OrderedValue> cacheMap = new HashMap<Object, OrderedValue>();
/** The removal event. */
private volatile transient EventDispatch2 removalEvent;
/**
* Ascending timestamp order. First is least recently used.
*/
private final TreeSet<OrderedValue> timedSet = new TreeSet<OrderedValue>();
/** The current size. */
private int currentSize = 0;
/**
* Instantiates a new LRU cache.
*
* @param approxMaxSize
* the approx max size
*/
public LRUCache(int approxMaxSize) {
this.approxMaxSize = approxMaxSize;
this.removalEvent = new RemovalDispatch();
}
/**
* Read object.
*
* @param in
* the in
* @throws IOException
* Signals that an I/O exception has occurred.
* @throws ClassNotFoundException
* the class not found exception
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
// Need to initialize transient fields here.
this.removalEvent = new RemovalDispatch();
}
/** Gets the approx max size.
*
* @return the approx max size
*/
public int getApproxMaxSize() {
return approxMaxSize;
}
/** Sets the approx max size.
*
* @param approxMaxSize
* the new approx max size
*/
public void setApproxMaxSize(int approxMaxSize) {
this.approxMaxSize = approxMaxSize;
}
/**
* Put.
*
* @param key
* the key
* @param value
* the value
* @param approxSize
* the approx size
*/
public void put(Object key, Object value, int approxSize) {
if (approxSize > this.approxMaxSize) {
// Can't be inserted.
return;
}
OrderedValue ordVal = this.cacheMap.get(key);
if (ordVal != null) {
if (ordVal.getValue() != value) {
this.removalEvent
.fireEvent(new RemovalEvent(this, ordVal.getValue()));
}
this.currentSize += (approxSize - ordVal.getApproximateSize());
this.timedSet.remove(ordVal);
ordVal.setApproximateSize(approxSize);
ordVal.setValue(value);
ordVal.touch();
this.timedSet.add(ordVal);
} else {
ordVal = new OrderedValue(key, value, approxSize);
this.cacheMap.put(key, ordVal);
this.timedSet.add(ordVal);
this.currentSize += approxSize;
}
while (this.currentSize > this.approxMaxSize) {
this.removeLRU();
}
}
/**
* Removes the lru.
*/
private void removeLRU() {
if (this.timedSet != null && this.timedSet.size() > 0) {
OrderedValue ordVal = this.timedSet.first();
if (ordVal != null) {
this.removalEvent
.fireEvent(new RemovalEvent(this, ordVal.getValue()));
if (this.timedSet.remove(ordVal)) {
this.cacheMap.remove(ordVal.getKey());
this.currentSize -= ordVal.getApproximateSize();
} else {
throw new IllegalStateException(
"Could not remove existing tree node.");
}
} else {
throw new IllegalStateException(
"Cannot remove LRU since the cache is empty.");
}
}
}
/**
* Gets the.
*
* @param key
* the key
* @return the object
*/
public Object get(Object key) {
OrderedValue ordVal = this.cacheMap.get(key);
if (ordVal != null) {
this.timedSet.remove(ordVal);
ordVal.touch();
this.timedSet.add(ordVal);
return ordVal.getValue();
} else {
return null;
}
}
/**
* Removes the.
*
* @param key
* the key
* @return the object
*/
public Object remove(Object key) {
OrderedValue ordVal = this.cacheMap.get(key);
if (ordVal != null) {
this.removalEvent
.fireEvent(new RemovalEvent(this, ordVal.getValue()));
this.currentSize -= ordVal.getApproximateSize();
this.timedSet.remove(ordVal);
return ordVal.getValue();
} else {
return null;
}
}
/**
* Adds the removal listener.
*
* @param listener
* the listener
*/
public void addRemovalListener(RemovalListener listener) {
this.removalEvent.addListener(listener);
}
/**
* Removes the removal listener.
*
* @param listener
* the listener
*/
public void removeRemovalListener(RemovalListener listener) {
this.removalEvent.removeListener(listener);
}
/** Gets the approx size.
*
* @return the approx size
*/
public int getApproxSize() {
return this.currentSize;
}
/** Gets the num entries.
*
* @return the num entries
*/
public int getNumEntries() {
return this.cacheMap.size();
}
/** Gets the entry info list.
*
* @return the entry info list
*/
public List<EntryInfo> getEntryInfoList() {
List<EntryInfo> list = new ArrayList<EntryInfo>();
Iterator<OrderedValue> i = this.cacheMap.values().iterator();
while (i.hasNext()) {
OrderedValue ov = i.next();
Object value = ov.getValue();
Class<?> vc = value == null ? null : value.getClass();
list.add(new EntryInfo(vc, ov.getApproximateSize()));
}
return list;
}
}