/*
* Copyright (C) 2006 http://www.chaidb.org
*
* 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 version 2
* 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 Public License for more details.
*
*/
package org.chaidb.db.helper.cache.algorithm;
import org.chaidb.db.helper.cache.measurable.Measurable;
import org.chaidb.db.helper.cache.measurable.SizeTooLargeException;
import java.util.Iterator;
import java.util.LinkedHashMap;
public class MeasurableFIFO implements MeasurableReplacementAlgorithm {
public static final String ALGORITHM_NAME = "FIFO";
static final int DEFAULT_INITIAL_CAPACITY = 16;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public MeasurableFIFO(long sizeBoundaryInBytes) {
if (sizeBoundaryInBytes <= 0) {
throw new IllegalArgumentException("cacheSize must be larger than 0");
} else {
this.sizeBoundaryInBytes = sizeBoundaryInBytes;
this.map = new LinkedHashMap(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, false);
}
}
public long getSizeBoundary() {
return this.sizeBoundaryInBytes;
}
public long getRetainedSize() {
return this.retainedSizeInBytes;
}
public boolean containsKey(Measurable key) {
return map.containsKey(key);
}
public Measurable put(Measurable key, Measurable value) throws SizeTooLargeException {
final long keySize = (key == null) ? 0 : key.getRetainedSize();
final long valueSize = (value == null) ? 0 : value.getRetainedSize();
final long sizeToBePlused;
// check if specified key exists
final boolean keyExists = map.containsKey(key);
if (keyExists) {
Measurable existingValue = (Measurable) map.get(key);
final long existingValueSize = (existingValue == null) ? 0 : existingValue.getRetainedSize();
sizeToBePlused = valueSize - existingValueSize;
} else {
sizeToBePlused = keySize + valueSize;
}
// throw exception if size of key/value pair is even larger than the max capacity
if (keySize + valueSize > sizeBoundaryInBytes) {
throw new SizeTooLargeException("required=" + sizeToBePlused + "; max capacity=" + sizeBoundaryInBytes);
}
// shortcut for key/value pair whose size equals to the max capacity
if (keySize + valueSize == sizeBoundaryInBytes) {
map.clear();
retainedSizeInBytes = sizeBoundaryInBytes;
return (Measurable) map.put(key, value);
}
if (sizeToBePlused + retainedSizeInBytes <= sizeBoundaryInBytes) {
retainedSizeInBytes += sizeToBePlused;
return (Measurable) map.put(key, value);
} else { // have to kickout some existing key'value pairs
final long smallestSizeToKickout = sizeToBePlused + retainedSizeInBytes - sizeBoundaryInBytes;
long kickoutSize = 0;
while (kickoutSize < smallestSizeToKickout) {
// Get the eldest key.
Measurable kickoutKey = getEldestKey();
// If no more key available, break out.
if (kickoutKey == NO_KEY) {
break;
}
// Remove the key/value pair.
Measurable kickoutValue = (Measurable) map.remove(kickoutKey);
if ((key == kickoutKey) || (key != null && key.equals(kickoutKey))) {
// If existing key is kicked out, don't count their size into kickout size.
// Because it will be added back later.
} else {
// Calculate size of key/value pair which has been kicked out
final long kickoutKeySize = (kickoutKey == null) ? 0 : kickoutKey.getRetainedSize();
final long kickoutValueSize = (kickoutValue == null) ? 0 : kickoutValue.getRetainedSize();
kickoutSize += (kickoutKeySize + kickoutValueSize);
}
}
retainedSizeInBytes += (sizeToBePlused - kickoutSize);
return (Measurable) map.put(key, value);
}
}
public Measurable get(Measurable key) {
return (Measurable) map.get(key);
}
public Measurable remove(Measurable key) {
// check if specified key exists
final boolean keyExists = map.containsKey(key);
if (keyExists) {
Measurable value = (Measurable) map.remove(key);
final long keySize = (key == null) ? 0 : key.getRetainedSize();
final long valueSize = (value == null) ? 0 : value.getRetainedSize();
retainedSizeInBytes -= (keySize + valueSize);
return value;
} else {
return null;
}
}
public void clear() {
retainedSizeInBytes = 0;
map.clear();
}
public long calculateRetainedSize() {
java.util.HashMap copy = new java.util.HashMap(map);
Iterator keyIterator = copy.keySet().iterator();
long retainedSize = 0;
while (keyIterator.hasNext()) {
Measurable key = (Measurable) keyIterator.next();
Measurable value = (Measurable) copy.get(key);
final long keySize = (key == null) ? 0 : key.getRetainedSize();
final long valueSize = (value == null) ? 0 : value.getRetainedSize();
retainedSize += (keySize + valueSize);
}
return retainedSize;
}
protected Measurable getEldestKey() {
Iterator keyIterator = map.keySet().iterator();
if (keyIterator.hasNext()) {
return (Measurable) keyIterator.next();
} else {
return NO_KEY;
}
}
private static final Measurable NO_KEY = new Measurable() {
public long getRetainedSize() {
return 0L;
}
};
private final long sizeBoundaryInBytes;
private long retainedSizeInBytes = 0;
private LinkedHashMap map;
}