com.ibm.commons.util.SystemCache Maven / Gradle / Ivy
/*
* © Copyright IBM Corp. 2012-2013
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.ibm.commons.util;
import com.ibm.commons.Platform;
/**
* System cache using a LRU mechanism.
*
* This cache is used to hold a certain number of entries that can be reused.
* For example, it can be used to cache compiled version of JavaScript snippets
* of code, or XPath definition.
*
* @ibm-api
*/
public final class SystemCache {
private String name;
private int maxSize;
private int size;
private MapEntry listStart;
private MapEntry listEnd;
private MapEntry[] entries;
// Some performance tuning information
private long accessed; // Number of times it had been accessed
private long incache; // Number of times
private long added; // Number of times an entry had been added to the cache
private long discarded; // Number of items discarded
private static class MapEntry {
boolean inCache;
// List in the Hash table
MapEntry prevHash;
MapEntry nextHash;
// List in the Linked list
MapEntry prevList;
MapEntry nextList;
int hashCode;
String key;
Object value;
MapEntry( String key, Object value ) {
this.key = key;
this.value = value;
this.hashCode = key.hashCode();
}
}
/**
* @ibm-api
*/
public SystemCache(String name, int maxSize) {
this.name = name;
this.maxSize = maxSize;
this.entries = new MapEntry[211];
}
/**
* @ibm-api
*/
public SystemCache(String name, int maxSize, String propertyName) {
this.name = name;
if(StringUtil.isNotEmpty(propertyName)) {
try {
String value = Platform.getInstance().getProperty(propertyName);
if(StringUtil.isNotEmpty(value)) {
int iv = Integer.parseInt(value);
if(iv>0) {
maxSize = iv;
}
}
} catch(Throwable ex) {}
}
this.maxSize = maxSize;
this.entries = new MapEntry[211];
}
/**
* @ibm-api
*/
public String getName() {
return name;
}
/**
* @ibm-api
*/
public long getAccessedTimes() {
return accessed;
}
/**
* @ibm-api
*/
public long getInCacheTimes() {
return incache;
}
/**
* @ibm-api
*/
public long getAddedTimes() {
return added;
}
/**
* @ibm-api
*/
public long getDiscardedTimes() {
return discarded;
}
/**
* @ibm-api
*/
public int size() {
return size;
}
/**
* @ibm-api
*/
public int getCapacity() {
return maxSize;
}
/**
* @ibm-api
*/
public synchronized void clear() {
this.entries = new MapEntry[211];
this.listStart = null;
this.listEnd = null;
this.size = 0;
}
/**
* @ibm-api
*/
public void remove(String key) {
// Tuning info
accessed++;
//TDiag.trace( "Getting '{0}'", key );
MapEntry e = getEntry(key);
if( e!=null ) {
incache++;
synchronized(this) {
// The entry might have been removed when it was not synchronized
// In this case, we should just re-add it
if(e.inCache) {
removeEntry(e);
}
}
}
}
/**
* @ibm-api
*/
public Object get( String key ) {
// Tuning info
accessed++;
//TDiag.trace( "Getting '{0}'", key );
MapEntry e = getEntry(key);
if( e!=null ) {
incache++;
synchronized(this) {
// The entry might have been removed when it was not synchronized
// In this case, we should just re-add it
if(e.inCache) {
moveToStart(e);
} else {
put(e);
}
}
return e.value;
}
return null;
}
/**
* @ibm-api
*/
public synchronized void put( String key, Object value ) {
if( maxSize>0 ) {
MapEntry e = getEntry(key);
if( e!=null ) {
e.value = value;
moveToStart(e);
} else {
e = new MapEntry(key,value);
put(e);
}
}
}
private void put( MapEntry e ) {
e.inCache = true;
// Remove the oldest one?
if( size==maxSize ) {
removeEntry(listEnd);
}
// Insert the new entry in the HashTable
int slot = getSlot(e.hashCode);
e.nextHash = entries[slot];
if(e.nextHash!=null) {
e.nextHash.prevHash = e;
}
entries[slot] = e;
// And in the list
if( listStart!=null ) {
listStart.prevList = e;
}
e.nextList = listStart;
listStart = e;
if( listEnd==null ) {
listEnd = e;
}
size++;
// Tuning info
added++;
}
private final int getSlot(int hashCode) {
return (hashCode & 0x7FFFFFFF) % entries.length;
}
private final MapEntry getEntry( String key ) {
int hashCode = key.hashCode();
for( MapEntry e=entries[getSlot(hashCode)]; e!=null; e=e.nextHash ) {
if( e.hashCode==hashCode && e.key.equals(key) ) {
return e;
}
}
return null;
}
private final void moveToStart( MapEntry e ) {
if( e!=listStart ) {
// Remove it from the list
e.prevList.nextList = e.nextList;
if( e.nextList!=null ) {
e.nextList.prevList = e.prevList;
} else {
listEnd = e.prevList;
}
// And add it a the top
listStart.prevList = e;
e.nextList = listStart;
listStart = e;
}
}
private final void removeEntry(MapEntry e) {
e.inCache = false;
// Remove the entry from the hashtable
if(e.prevHash!=null) {
e.prevHash.nextHash = e.nextHash;
} else {
entries[getSlot(e.hashCode)] = e.nextHash;
}
if(e.nextHash!=null) {
e.nextHash.prevHash = e.prevHash;
}
// Remove the entry from the linked list
if( e.prevList!=null ) {
e.prevList.nextList = e.nextList;
} else {
listStart = e.nextList;
}
if( e.nextList!=null ) {
e.nextList.prevList = e.prevList;
} else {
listEnd = e.prevList;
}
// Decrease the map count
size--;
// Tuning information
discarded++;
}
// FOR DEBUG ONLY
// private void dumpList() {
// TDiag.trace( "list({0}), start={1}, end={2}", TString.toString(size), listStart!=null ? listStart.key : "", listEnd!=null ? listEnd.key : "" );
// for( MapEntry e=listStart; e!=null; e=e.nextList ) {
// TDiag.trace( "{0}, prev={1}, next ={2}", e.key, e.prevList!=null ? e.prevList.key : "", e.nextList!=null ? e.nextList.key : "" );
// }
// }
}