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

591 lines
20 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 java.io.DataInput;
import java.io.DataOutput;
import java.io.IOError;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentNavigableMap;
/**
* An abstract class implementing most of DB.
* It also has some JDBM package protected stuff (getNamedRecord)
*/
abstract class DBAbstract implements DB {
/**
* Reserved slot for name directory recid.
*/
static final byte NAME_DIRECTORY_ROOT = 0;
/**
* Reserved slot for version number
*/
static final byte STORE_VERSION_NUMBER_ROOT = 1;
/**
* Reserved slot for recid where Serial class info is stored
*
* NOTE when introducing more roots, do not forget to update defrag
*/
static final byte SERIAL_CLASS_INFO_RECID_ROOT = 2;
/** to prevent double instances of the same collection, we use weak value map
*
* //TODO what to do when there is rollback?
* //TODO clear on close
*/
final private Map<String,WeakReference<Object>> collections = new HashMap<String,WeakReference<Object>>();
/**
* Inserts a new record using a custom serializer.
*
* @param obj the object for the new record.
* @param serializer a custom serializer
* @return the rowid for the new record.
* @throws java.io.IOException when one of the underlying I/O operations fails.
*/
abstract <A> long insert(A obj, Serializer<A> serializer,boolean disableCache) throws IOException;
/**
* Deletes a record.
*
* @param recid the rowid for the record that should be deleted.
* @throws java.io.IOException when one of the underlying I/O operations fails.
*/
abstract void delete(long recid) throws IOException;
/**
* Updates a record using a custom serializer.
* If given recid does not exist, IOException will be thrown before/during commit (cache).
*
* @param recid the recid for the record that is to be updated.
* @param obj the new object for the record.
* @param serializer a custom serializer
* @throws java.io.IOException when one of the underlying I/O operations fails
*/
abstract <A> void update(long recid, A obj, Serializer<A> serializer)
throws IOException;
/**
* Fetches a record using a custom serializer.
*
* @param recid the recid for the record that must be fetched.
* @param serializer a custom serializer
* @return the object contained in the record, null if given recid does not exist
* @throws java.io.IOException when one of the underlying I/O operations fails.
*/
abstract <A> A fetch(long recid, Serializer<A> serializer)
throws IOException;
/**
* Fetches a record using a custom serializer and optionaly disabled cache
*
* @param recid the recid for the record that must be fetched.
* @param serializer a custom serializer
* @param disableCache true to disable any caching mechanism
* @return the object contained in the record, null if given recid does not exist
* @throws java.io.IOException when one of the underlying I/O operations fails.
*/
abstract <A> A fetch(long recid, Serializer<A> serializer, boolean disableCache)
throws IOException;
public long insert(Object obj) throws IOException {
return insert(obj, defaultSerializer(),false);
}
public void update(long recid, Object obj) throws IOException {
update(recid, obj, defaultSerializer());
}
synchronized public <A> A fetch(long recid) throws IOException {
return (A) fetch(recid, defaultSerializer());
}
synchronized public <K, V> ConcurrentMap<K, V> getHashMap(String name) {
Object o = getCollectionInstance(name);
if(o!=null)
return (ConcurrentMap<K, V>) o;
try {
long recid = getNamedObject(name);
if(recid == 0) return null;
HTree tree = fetch(recid);
tree.setPersistenceContext(this);
if(!tree.hasValues()){
throw new ClassCastException("HashSet is not HashMap");
}
collections.put(name,new WeakReference<Object>(tree));
return tree;
} catch (IOException e) {
throw new IOError(e);
}
}
synchronized public <K, V> ConcurrentMap<K, V> createHashMap(String name) {
return createHashMap(name, null, null);
}
public synchronized <K, V> ConcurrentMap<K, V> createHashMap(String name, Serializer<K> keySerializer, Serializer<V> valueSerializer) {
try {
assertNameNotExist(name);
HTree<K, V> tree = new HTree(this, keySerializer, valueSerializer,true);
long recid = insert(tree);
setNamedObject(name, recid);
collections.put(name,new WeakReference<Object>(tree));
return tree;
} catch (IOException e) {
throw new IOError(e);
}
}
public synchronized <K> Set<K> getHashSet(String name) {
Object o = getCollectionInstance(name);
if(o!=null)
return (Set<K>) o;
try {
long recid = getNamedObject(name);
if(recid == 0) return null;
HTree tree = fetch(recid);
tree.setPersistenceContext(this);
if(tree.hasValues()){
throw new ClassCastException("HashMap is not HashSet");
}
Set<K> ret = new HTreeSet(tree);
collections.put(name,new WeakReference<Object>(ret));
return ret;
} catch (IOException e) {
throw new IOError(e);
}
}
public synchronized <K> Set<K> createHashSet(String name) {
return createHashSet(name, null);
}
public synchronized <K> Set<K> createHashSet(String name, Serializer<K> keySerializer) {
try {
assertNameNotExist(name);
HTree<K, Object> tree = new HTree(this, keySerializer, null,false);
long recid = insert(tree);
setNamedObject(name, recid);
Set<K> ret = new HTreeSet<K>(tree);
collections.put(name,new WeakReference<Object>(ret));
return ret;
} catch (IOException e) {
throw new IOError(e);
}
}
synchronized public <K, V> ConcurrentNavigableMap<K, V> getTreeMap(String name) {
Object o = getCollectionInstance(name);
if(o!=null)
return (ConcurrentNavigableMap<K, V> ) o;
try {
long recid = getNamedObject(name);
if(recid == 0) return null;
BTree t = BTree.<K, V>load(this, recid);
if(!t.hasValues())
throw new ClassCastException("TreeSet is not TreeMap");
ConcurrentNavigableMap<K,V> ret = new BTreeMap<K, V>(t,false); //TODO put readonly flag here
collections.put(name,new WeakReference<Object>(ret));
return ret;
} catch (IOException e) {
throw new IOError(e);
}
}
synchronized public <K extends Comparable, V> ConcurrentNavigableMap<K, V> createTreeMap(String name) {
return createTreeMap(name, null, null, null);
}
public synchronized <K, V> ConcurrentNavigableMap<K, V> createTreeMap(String name,
Comparator<K> keyComparator,
Serializer<K> keySerializer,
Serializer<V> valueSerializer) {
try {
assertNameNotExist(name);
BTree<K, V> tree = BTree.createInstance(this, keyComparator, keySerializer, valueSerializer,true);
setNamedObject(name, tree.getRecid());
ConcurrentNavigableMap<K,V> ret = new BTreeMap<K, V>(tree,false); //TODO put readonly flag here
collections.put(name,new WeakReference<Object>(ret));
return ret;
} catch (IOException e) {
throw new IOError(e);
}
}
public synchronized <K> NavigableSet<K> getTreeSet(String name) {
Object o = getCollectionInstance(name);
if(o!=null)
return (NavigableSet<K> ) o;
try {
long recid = getNamedObject(name);
if(recid == 0) return null;
BTree t = BTree.<K, Object>load(this, recid);
if(t.hasValues())
throw new ClassCastException("TreeMap is not TreeSet");
BTreeSet<K> ret = new BTreeSet<K>(new BTreeMap(t,false));
collections.put(name,new WeakReference<Object>(ret));
return ret;
} catch (IOException e) {
throw new IOError(e);
}
}
public synchronized <K> NavigableSet<K> createTreeSet(String name) {
return createTreeSet(name, null, null);
}
public synchronized <K> NavigableSet<K> createTreeSet(String name, Comparator<K> keyComparator, Serializer<K> keySerializer) {
try {
assertNameNotExist(name);
BTree<K, Object> tree = BTree.createInstance(this, keyComparator, keySerializer, null,false);
setNamedObject(name, tree.getRecid());
BTreeSet<K> ret = new BTreeSet<K>(new BTreeMap(tree,false));
collections.put(name,new WeakReference<Object>(ret));
return ret;
} catch (IOException e) {
throw new IOError(e);
}
}
synchronized public <K> List<K> createLinkedList(String name) {
return createLinkedList(name, null);
}
synchronized public <K> List<K> createLinkedList(String name, Serializer<K> serializer) {
try {
assertNameNotExist(name);
//allocate record and overwrite it
LinkedList2<K> list = new LinkedList2<K>(this, serializer);
long recid = insert(list);
setNamedObject(name, recid);
collections.put(name,new WeakReference<Object>(list));
return list;
} catch (IOException e) {
throw new IOError(e);
}
}
synchronized public <K> List<K> getLinkedList(String name) {
Object o = getCollectionInstance(name);
if(o!=null)
return (List<K> ) o;
try {
long recid = getNamedObject(name);
if(recid == 0) return null;
LinkedList2<K> list = (LinkedList2<K>) fetch(recid);
list.setPersistenceContext(this);
collections.put(name,new WeakReference<Object>(list));
return list;
} catch (IOException e) {
throw new IOError(e);
}
}
private synchronized Object getCollectionInstance(String name){
WeakReference ref = collections.get(name);
if(ref==null)return null;
Object o = ref.get();
if(o != null) return o;
//already GCed
collections.remove(name);
return null;
}
private void assertNameNotExist(String name) throws IOException {
if (getNamedObject(name) != 0)
throw new IllegalArgumentException("Object with name '" + name + "' already exists");
}
/**
* Obtain the record id of a named object. Returns 0 if named object
* doesn't exist.
* Named objects are used to store Map views and other well known objects.
*/
synchronized protected long getNamedObject(String name) throws IOException{
long nameDirectory_recid = getRoot(NAME_DIRECTORY_ROOT);
if(nameDirectory_recid == 0){
return 0;
}
HTree<String,Long> m = fetch(nameDirectory_recid);
Long res = m.get(name);
if(res == null)
return 0;
return res;
}
/**
* Set the record id of a named object.
* Named objects are used to store Map views and other well known objects.
*/
synchronized protected void setNamedObject(String name, long recid) throws IOException{
long nameDirectory_recid = getRoot(NAME_DIRECTORY_ROOT);
HTree<String,Long> m = null;
if(nameDirectory_recid == 0){
//does not exists, create it
m = new HTree<String, Long>(this,null,null,true);
nameDirectory_recid = insert(m);
setRoot(NAME_DIRECTORY_ROOT,nameDirectory_recid);
}else{
//fetch it
m = fetch(nameDirectory_recid);
}
m.put(name,recid);
}
synchronized public Map<String,Object> getCollections(){
try{
Map<String,Object> ret = new LinkedHashMap<String, Object>();
long nameDirectory_recid = getRoot(NAME_DIRECTORY_ROOT);
if(nameDirectory_recid==0)
return ret;
HTree<String,Long> m = fetch(nameDirectory_recid);
for(Map.Entry<String,Long> e:m.entrySet()){
Object o = fetch(e.getValue());
if(o instanceof BTree){
if(((BTree) o).hasValues)
o = getTreeMap(e.getKey());
else
o = getTreeSet(e.getKey());
}
else if( o instanceof HTree){
if(((HTree) o).hasValues)
o = getHashMap(e.getKey());
else
o = getHashSet(e.getKey());
}
ret.put(e.getKey(), o);
}
return Collections.unmodifiableMap(ret);
}catch(IOException e){
throw new IOError(e);
}
}
synchronized public void deleteCollection(String name){
try{
long nameDirectory_recid = getRoot(NAME_DIRECTORY_ROOT);
if(nameDirectory_recid==0)
throw new IOException("Collection not found");
HTree<String,Long> dir = fetch(nameDirectory_recid);
Long recid = dir.get(name);
if(recid == null) throw new IOException("Collection not found");
Object o = fetch(recid);
//we can not use O instance since it is not correctly initialized
if(o instanceof LinkedList2){
LinkedList2 l = (LinkedList2) o;
l.clear();
delete(l.rootRecid);
}else if(o instanceof BTree){
((BTree) o).clear();
} else if( o instanceof HTree){
HTree t = (HTree) o;
t.clear();
HTreeDirectory n = (HTreeDirectory) fetch(t.rootRecid,t.SERIALIZER);
n.deleteAllChildren();
delete(t.rootRecid);
}else{
throw new InternalError("unknown collection type: "+(o==null?null:o.getClass()));
}
delete(recid);
collections.remove(name);
dir.remove(name);
}catch(IOException e){
throw new IOError(e);
}
}
/** we need to set reference to this DB instance, so serializer needs to be here*/
final Serializer<Serialization> defaultSerializationSerializer = new Serializer<Serialization>(){
public void serialize(DataOutput out, Serialization obj) throws IOException {
LongPacker.packLong(out,obj.serialClassInfoRecid);
SerialClassInfo.serializer.serialize(out,obj.registered);
}
public Serialization deserialize(DataInput in) throws IOException, ClassNotFoundException {
final long recid = LongPacker.unpackLong(in);
final ArrayList<SerialClassInfo.ClassInfo> classes = SerialClassInfo.serializer.deserialize(in);
return new Serialization(DBAbstract.this,recid,classes);
}
};
public synchronized Serializer defaultSerializer() {
try{
long serialClassInfoRecid = getRoot(SERIAL_CLASS_INFO_RECID_ROOT);
if (serialClassInfoRecid == 0) {
//allocate new recid
serialClassInfoRecid = insert(null,Utils.NULL_SERIALIZER,false);
//and insert new serializer
Serialization ser = new Serialization(this,serialClassInfoRecid,new ArrayList<SerialClassInfo.ClassInfo>());
update(serialClassInfoRecid,ser, defaultSerializationSerializer);
setRoot(SERIAL_CLASS_INFO_RECID_ROOT, serialClassInfoRecid);
return ser;
}else{
return fetch(serialClassInfoRecid,defaultSerializationSerializer);
}
} catch (IOException e) {
throw new IOError(e);
}
}
final protected void checkNotClosed(){
if(isClosed()) throw new IllegalStateException("db was closed");
}
protected abstract void setRoot(byte root, long recid);
protected abstract long getRoot(byte root);
synchronized public long collectionSize(Object collection){
if(collection instanceof BTreeMap){
BTreeMap t = (BTreeMap) collection;
if(t.fromKey!=null|| t.toKey!=null) throw new IllegalArgumentException("collectionSize does not work on BTree submap");
return t.tree._entries;
}else if(collection instanceof HTree){
return ((HTree)collection).getRoot().size;
}else if(collection instanceof HTreeSet){
return collectionSize(((HTreeSet) collection).map);
}else if(collection instanceof BTreeSet){
return collectionSize(((BTreeSet) collection).map);
}else if(collection instanceof LinkedList2){
return ((LinkedList2)collection).getRoot().size;
}else{
throw new IllegalArgumentException("Not JDBM collection");
}
}
void addShutdownHook(){
if(shutdownCloseThread!=null){
shutdownCloseThread = new ShutdownCloseThread();
Runtime.getRuntime().addShutdownHook(shutdownCloseThread);
}
}
public void close(){
if(shutdownCloseThread!=null){
Runtime.getRuntime().removeShutdownHook(shutdownCloseThread);
shutdownCloseThread.dbToClose = null;
shutdownCloseThread = null;
}
}
ShutdownCloseThread shutdownCloseThread = null;
private static class ShutdownCloseThread extends Thread{
DBAbstract dbToClose = null;
ShutdownCloseThread(){
super("JDBM shutdown");
}
public void run(){
if(dbToClose!=null && !dbToClose.isClosed()){
dbToClose.shutdownCloseThread = null;
dbToClose.close();
}
}
}
synchronized public void rollback() {
try {
for(WeakReference<Object> o:collections.values()){
Object c = o.get();
if(c != null && c instanceof BTreeMap){
//reload tree
BTreeMap m = (BTreeMap) c;
m.tree = fetch(m.tree.getRecid());
}
if(c != null && c instanceof BTreeSet){
//reload tree
BTreeSet m = (BTreeSet) c;
m.map.tree = fetch(m.map.tree.getRecid());
}
}
} catch (IOException e) {
throw new IOError(e);
}
}
}