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);