SCViewer/com.minres.scviewer.databas.../src/org/apache/jdbm/DBCacheMRU.java

351 lines
8.6 KiB
Java

/*******************************************************************************
* Copyright 2010 Cees De Groot, Alex Boisvert, Jan Kotek
*
* 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 org.apache.jdbm;
import javax.crypto.Cipher;
import java.io.IOException;
/**
* A DB wrapping and caching another DB.
*
* @author Jan Kotek
* @author Alex Boisvert
* @author Cees de Groot
*
* TODO add 'cache miss' statistics
*/
class DBCacheMRU
extends DBCache {
private static final boolean debug = false;
/**
* Cached object hashtable
*/
protected LongHashMap<CacheEntry> _hash;
/**
* Maximum number of objects in the cache.
*/
protected int _max;
/**
* Beginning of linked-list of cache elements. First entry is element
* which has been used least recently.
*/
protected CacheEntry _first;
/**
* End of linked-list of cache elements. Last entry is element
* which has been used most recently.
*/
protected CacheEntry _last;
/**
* Construct a CacheRecordManager wrapping another DB and
* using a given cache policy.
*/
public DBCacheMRU(String filename, boolean readonly, boolean transactionDisabled,
Cipher cipherIn, Cipher cipherOut, boolean useRandomAccessFile,
boolean deleteFilesAfterClose, int cacheMaxRecords, boolean lockingDisabled) {
super(filename, readonly, transactionDisabled,
cipherIn, cipherOut, useRandomAccessFile,
deleteFilesAfterClose,lockingDisabled);
_hash = new LongHashMap<CacheEntry>(cacheMaxRecords);
_max = cacheMaxRecords;
}
public synchronized <A> A fetch(long recid, Serializer<A> serializer, boolean disableCache) throws IOException {
if (disableCache)
return super.fetch(recid, serializer, disableCache);
else
return fetch(recid, serializer);
}
public synchronized void delete(long recid)
throws IOException {
checkNotClosed();
super.delete(recid);
synchronized (_hash){
CacheEntry entry = _hash.get(recid);
if (entry != null) {
removeEntry(entry);
_hash.remove(entry._recid);
}
_hashDirties.remove(recid);
}
if(super.needsAutoCommit())
commit();
}
public synchronized <A> void update(final long recid, final A obj, final Serializer<A> serializer) throws IOException {
checkNotClosed();
synchronized (_hash){
//remove entry if it already exists
CacheEntry entry = cacheGet(recid);
if (entry != null) {
_hash.remove(recid);
removeEntry(entry);
}
//check if entry is in dirties, in this case just update its object
DirtyCacheEntry e = _hashDirties.get(recid);
if(e!=null){
if(recid!=e._recid) throw new Error();
e._obj = obj;
e._serializer = serializer;
return;
}
//create new dirty entry
e = new DirtyCacheEntry();
e._recid = recid;
e._obj = obj;
e._serializer = serializer;
_hashDirties.put(recid,e);
}
if(super.needsAutoCommit())
commit();
}
public synchronized <A> A fetch(long recid, Serializer<A> serializer)
throws IOException {
checkNotClosed();
final CacheEntry entry = cacheGet(recid);
if (entry != null) {
return (A) entry._obj;
}
//check dirties
final DirtyCacheEntry entry2 = _hashDirties.get(recid);
if(entry2!=null){
return (A) entry2._obj;
}
A value = super.fetch(recid, serializer);
if(super.needsAutoCommit())
commit();
//put record into MRU cache
cachePut(recid, value);
return value;
}
public synchronized void close() {
if(isClosed())
return;
updateCacheEntries();
super.close();
_hash = null;
}
public synchronized void rollback() {
// discard all cache entries since we don't know which entries
// where part of the transaction
synchronized (_hash){
_hash.clear();
_first = null;
_last = null;
}
super.rollback();
}
/**
* Obtain an object in the cache
*/
protected CacheEntry cacheGet(long key) {
synchronized (_hash){
CacheEntry entry = _hash.get(key);
if ( entry != null && _last != entry) {
//touch entry
removeEntry(entry);
addEntry(entry);
}
return entry;
}
}
/**
* Place an object in the cache.
*
* @throws IOException
*/
protected void cachePut(final long recid, final Object value) throws IOException {
synchronized (_hash){
CacheEntry entry = _hash.get(recid);
if (entry != null) {
entry._obj = value;
//touch entry
if (_last != entry) {
removeEntry(entry);
addEntry(entry);
}
} else {
if (_hash.size() >= _max) {
// purge and recycle entry
entry = purgeEntry();
entry._recid = recid;
entry._obj = value;
} else {
entry = new CacheEntry(recid, value);
}
addEntry(entry);
_hash.put(entry._recid, entry);
}
}
}
/**
* Add a CacheEntry. Entry goes at the end of the list.
*/
protected void addEntry(CacheEntry entry) {
synchronized (_hash){
if (_first == null) {
_first = entry;
_last = entry;
} else {
_last._next = entry;
entry._previous = _last;
_last = entry;
}
}
}
/**
* Remove a CacheEntry from linked list
*/
protected void removeEntry(CacheEntry entry) {
synchronized (_hash){
if (entry == _first) {
_first = entry._next;
}
if (_last == entry) {
_last = entry._previous;
}
CacheEntry previous = entry._previous;
CacheEntry next = entry._next;
if (previous != null) {
previous._next = next;
}
if (next != null) {
next._previous = previous;
}
entry._previous = null;
entry._next = null;
}
}
/**
* Purge least recently used object from the cache
*
* @return recyclable CacheEntry
*/
protected CacheEntry purgeEntry() {
synchronized (_hash){
CacheEntry entry = _first;
if (entry == null)
return new CacheEntry(-1, null);
removeEntry(entry);
_hash.remove(entry._recid);
entry._obj = null;
return entry;
}
}
@SuppressWarnings("unchecked")
static final class CacheEntry {
protected long _recid;
protected Object _obj;
protected CacheEntry _previous;
protected CacheEntry _next;
CacheEntry(long recid, Object obj) {
_recid = recid;
_obj = obj;
}
}
public void clearCache() {
if(debug)
System.err.println("DBCache: Clear cache");
// discard all cache entries since we don't know which entries
// where part of the transaction
synchronized (_hash){
_hash.clear();
_first = null;
_last = null;
//clear dirties
updateCacheEntries();
}
}
}