/******************************************************************************* * 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 de.mxro.thrd.jdbm2V22.htree; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import de.mxro.thrd.jdbm2V22.*; import de.mxro.thrd.jdbm2V22.helper.JdbmBase; import de.mxro.thrd.jdbm2V22.helper.Serialization; /** * Persistent hashtable implementation for PageManager. * Implemented as an H*Tree structure. * * WARNING! If this instance is used in a transactional context, it * *must* be discarded after a rollback. * * @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a> * @version $Id: HTree.java,v 1.3 2005/06/25 23:12:32 doomdark Exp $ */ public class HTree<K,V> implements JdbmBase<K,V> { final Serializer SERIALIZER = new Serializer<HashNode>() { public HashNode deserialize(SerializerInput ds) throws IOException { try{ int i = ds.read(); if(i == Serialization.HTREE_BUCKET){ //is HashBucket? HashBucket ret = new HashBucket(HTree.this); ret.readExternal(ds); if(ds.available()!=0 && ds.read()!=-1) // -1 is fix for compression, not sure what is happening throw new InternalError("bytes left: "+ds.available()); return ret; }else if( i == Serialization.HTREE_DIRECTORY){ HashDirectory ret = new HashDirectory(HTree.this); ret.readExternal(ds); if(ds.available()!=0 && ds.read()!=-1) // -1 is fix for compression, not sure what is happening throw new InternalError("bytes left: "+ds.available()); return ret; }else { throw new InternalError("Wrong HTree header: "+i); } }catch(ClassNotFoundException e){ throw new IOException(e); } } public void serialize(SerializerOutput out, HashNode obj) throws IOException { if(obj.getClass() == HashBucket.class){ out.write(Serialization.HTREE_BUCKET); HashBucket b = (HashBucket) obj; b.writeExternal(out); }else{ out.write(Serialization.HTREE_DIRECTORY); HashDirectory n = (HashDirectory) obj; n.writeExternal(out); } } }; /** * Root hash directory. */ private HashDirectory<K,V> _root; /** * Listeners which are notified about changes in records */ protected List<RecordListener<K,V>> recordListeners = new ArrayList<RecordListener<K,V>>(); /** * Serializer used to serialize index keys (optional) */ protected Serializer<K> keySerializer; /** * Serializer used to serialize index values (optional) */ protected Serializer<V> valueSerializer; public Serializer<K> getKeySerializer() { return keySerializer; } public Serializer<V> getValueSerializer() { return valueSerializer; } HTree() { } /** * Create a persistent hashtable. * * @param recman Record manager used for persistence. */ public static <K,V> HTree<K,V> createInstance( RecordManager recman ) throws IOException { return createInstance(recman, null, null); } /** * Create a persistent hashtable. * * @param recman Record manager used for persistence. */ public static <K,V> HTree<K,V> createInstance( RecordManager recman, Serializer<K> keySerializer, Serializer<V> valueSerializer ) throws IOException { HashDirectory<K,V> root; long recid; HTree<K,V> tree = new HTree<K,V>(); tree.keySerializer = keySerializer; tree.valueSerializer = valueSerializer; tree._root = new HashDirectory<K,V>( tree, (byte) 0 ); recid = recman.insert( tree._root, tree.SERIALIZER ); tree._root.setPersistenceContext(recman, recid); return tree; } /** * Load a persistent hashtable * * @param recman RecordManager used to store the persistent hashtable * @param root_recid Record id of the root directory of the HTree */ public static <K,V> HTree<K,V> load( RecordManager recman, long root_recid) throws IOException { return load(recman, root_recid, null, null); } /** * Load a persistent hashtable * * @param recman RecordManager used to store the persistent hashtable * @param root_recid Record id of the root directory of the HTree */ public static <K,V> HTree<K,V> load( RecordManager recman, long root_recid, Serializer<K> keySerializer, Serializer<V> valueSerializer ) throws IOException { HashDirectory<K,V> root; HTree<K,V> tree = new HTree<K,V>(); tree.keySerializer = keySerializer; tree.valueSerializer = valueSerializer; tree._root = (HashDirectory<K,V>) recman.fetch( root_recid, tree.SERIALIZER ); tree._root.setPersistenceContext( recman, root_recid ); return tree; } transient long hashEqualsIdentityCounter=0; /** * Associates the specified value with the specified key. * * @param key key with which the specified value is to be assocated. * @param value value to be associated with the specified key. */ public synchronized void put(K key, V value) throws IOException { V oldVal = null; if(!recordListeners.isEmpty()) oldVal = find(key); _root.put(key, value); if(oldVal == null){ for(RecordListener<K,V> r : recordListeners) r.recordInserted(key,value); }else{ for(RecordListener<K,V> r : recordListeners) r.recordUpdated(key,oldVal,value); } } /** * Returns the value which is associated with the given key. Returns * <code>null</code> if there is not association for this key. * * @param key key whose associated value is to be returned */ public synchronized V find(K key) throws IOException { return _root.get(key); } /** * Remove the value which is associated with the given key. If the * key does not exist, this method simply ignores the operation. * * @param key key whose associated value is to be removed */ public synchronized void remove(K key) throws IOException { V val = null; if(!recordListeners.isEmpty()) val = find(key); _root.remove(key); if(val!=null) for(RecordListener<K,V> r : recordListeners) r.recordRemoved(key,val); } /** * Returns an enumeration of the keys contained in this */ public synchronized Iterator<K> keys() throws IOException { return _root.keys(); } /** * Returns an enumeration of the values contained in this */ public synchronized Iterator<V> values() throws IOException { return _root.values(); } /** * Get the record identifier used to load this hashtable. */ public long getRecid() { return _root.getRecid(); } public HTreeMap<K,V> asMap(){ return new HTreeMap<K,V>(this,false); } /** * add RecordListener which is notified about record changes * @param listener */ public void addRecordListener(RecordListener<K,V> listener){ recordListeners.add(listener); } /** * remove RecordListener which is notified about record changes * @param listener */ public void removeRecordListener(RecordListener<K,V> listener){ recordListeners.remove(listener); } public RecordManager getRecordManager() { return _root.getRecordManager(); } }