blob: 80478762695ca099c7753da6c1e89bdf13b64dd2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.directory.server.core.partition.impl.btree.jdbm;
import jdbm.RecordManager;
import jdbm.btree.BTree;
import jdbm.helper.Serializer;
import jdbm.helper.TupleBrowser;
import org.apache.commons.collections.iterators.ArrayIterator;
import org.apache.directory.server.core.partition.impl.btree.KeyOnlyComparator;
import org.apache.directory.server.core.partition.impl.btree.NoDupsEnumeration;
import org.apache.directory.server.core.partition.impl.btree.Table;
import org.apache.directory.server.core.partition.impl.btree.Tuple;
import org.apache.directory.server.core.partition.impl.btree.TupleComparator;
import org.apache.directory.server.core.partition.impl.btree.TupleEnumeration;
import org.apache.directory.server.core.partition.impl.btree.TupleRenderer;
import org.apache.directory.server.schema.SerializableComparator;
import org.apache.directory.shared.ldap.exception.LdapNamingException;
import org.apache.directory.shared.ldap.message.ResultCodeEnum;
import org.apache.directory.shared.ldap.util.EmptyEnumeration;
import org.apache.directory.shared.ldap.util.SingletonEnumeration;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* A jdbm Btree wrapper that enables duplicate sorted keys using collections.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev$
*/
public class JdbmTable implements Table
{
/** */
private static final String SZSUFFIX = "_btree_sz";
/** */
private final String name;
/** */
private final RecordManager recMan;
/** */
private final boolean allowsDuplicates;
/** */
private final TupleComparator comparator;
/** */
private int count = 0;
/** */
private BTree bt;
/** */
private TupleRenderer renderer;
private int numDupLimit = JdbmIndex.DEFAULT_DUPLICATE_LIMIT;
private Map<Long, BTree> duplicateBtrees = new HashMap<Long, BTree>();
// ------------------------------------------------------------------------
// C O N S T R U C T O R
// ------------------------------------------------------------------------
/**
* Creates a Jdbm BTree based tuple Table abstraction that enables
* duplicates.
*
* @param name the name of the table
* @param allowsDuplicates whether or not duplicates are enabled
* @param manager the record manager to be used for this table
* @param comparator a tuple comparator
* @throws NamingException if the table's file cannot be created
*/
public JdbmTable( String name, boolean allowsDuplicates, int numDupLimit,
RecordManager manager, TupleComparator comparator, Serializer keySerializer,
Serializer valueSerializer )
throws NamingException
{
this.numDupLimit = numDupLimit;
this.name = name;
this.recMan = manager;
this.comparator = comparator;
this.allowsDuplicates = allowsDuplicates;
long recId;
try
{
recId = recMan.getNamedObject( name );
}
catch ( IOException e )
{
NamingException ne = new NamingException();
ne.setRootCause( e );
throw ne;
}
try
{
//
// Load existing BTree
//
if ( recId != 0 )
{
bt = BTree.load( recMan, recId );
bt.setValueSerializer( valueSerializer );
recId = recMan.getNamedObject( name + SZSUFFIX );
count = ( ( Integer ) recMan.fetch( recId ) ).intValue();
}
else
{
bt = BTree.createInstance( recMan, comparator.getKeyComparator(), keySerializer, valueSerializer );
recId = bt.getRecid();
recMan.setNamedObject( name, recId );
recId = recMan.insert( new Integer( 0 ) );
recMan.setNamedObject( name + SZSUFFIX, recId );
}
}
catch ( IOException e )
{
NamingException ne = new NamingException();
ne.setRootCause( e );
throw ne;
}
}
/**
* Creates a Jdbm BTree based tuple Table abstraction without duplicates
* enabled using a simple key comparator.
*
* @param name the name of the table
* @param manager the record manager to be used for this table
* @param keyComparator a tuple comparator
* @throws NamingException if the table's file cannot be created
*/
public JdbmTable( String name, RecordManager manager, SerializableComparator keyComparator, Serializer keySerializer, Serializer valueSerializer )
throws NamingException
{
this( name, false, Integer.MAX_VALUE, manager, new KeyOnlyComparator( keyComparator ), keySerializer, valueSerializer );
}
// ------------------------------------------------------------------------
// Simple Table Properties
// ------------------------------------------------------------------------
/**
* @see org.apache.directory.server.core.partition.impl.btree.Table#getComparator()
*/
public TupleComparator getComparator()
{
return comparator;
}
/**
* @see org.apache.directory.server.core.partition.impl.btree.Table#isDupsEnabled()
*/
public boolean isDupsEnabled()
{
return allowsDuplicates;
}
/**
* @see org.apache.directory.server.core.partition.impl.btree.Table#getName()
*/
public String getName()
{
return name;
}
/**
* @see org.apache.directory.server.core.partition.impl.btree.Table#getRenderer()
*/
public TupleRenderer getRenderer()
{
return renderer;
}
/**
* @see Table#setRenderer(
* TupleRenderer)
*/
public void setRenderer( TupleRenderer renderer )
{
this.renderer = renderer;
}
/**
* @see Table#isSortedDupsEnabled()
*/
public boolean isSortedDupsEnabled()
{
// If duplicates are enabled than duplicates will be maintained in
// sorted order.
return allowsDuplicates;
}
// ------------------------------------------------------------------------
// Count Overloads
// ------------------------------------------------------------------------
/**
* @see Table#count(java.lang.Object, boolean)
*/
public int count( Object key, boolean isGreaterThan ) throws NamingException
{
// take a best guess
return count;
}
/**
* @see Table#count(java.lang.Object)
*/
public int count( Object key ) throws NamingException
{
if ( !allowsDuplicates )
{
if ( null == getRaw( key ) )
{
return 0;
}
else
{
return 1;
}
}
Object values = getRaw( key );
if ( values == null )
{
return 0;
}
// -------------------------------------------------------------------
// Handle the use of a TreeSet for storing duplicates
// -------------------------------------------------------------------
if ( values instanceof TreeSet )
{
return ( ( TreeSet ) values ).size();
}
// -------------------------------------------------------------------
// Handle the use of a BTree for storing duplicates
// -------------------------------------------------------------------
if ( values instanceof BTreeRedirect )
{
return getBTree( ( BTreeRedirect ) values ).size();
}
throw new IllegalStateException( "When using duplicate keys either a TreeSet or BTree is used for values." );
}
/**
* @see org.apache.directory.server.core.partition.impl.btree.Table#count()
*/
public int count() throws NamingException
{
return count;
}
// ------------------------------------------------------------------------
// get/has/put/remove Methods and Overloads
// ------------------------------------------------------------------------
/**
* @see Table#get(java.lang.Object)
*/
public Object get( Object key ) throws NamingException
{
if ( ! allowsDuplicates )
{
return getRaw( key );
}
Object values = getRaw( key );
if ( values == null )
{
return null;
}
if ( values instanceof TreeSet )
{
TreeSet set = ( TreeSet ) values;
if ( set.size() == 0 )
{
return null;
}
else
{
return set.first();
}
}
if ( values instanceof BTreeRedirect )
{
BTree tree = getBTree( ( BTreeRedirect ) values );
if ( tree.size() == 0 )
{
return null;
}
else
{
return firstKey( tree );
}
}
throw new IllegalStateException( "When using duplicate keys either a TreeSet or BTree is used for values." );
}
/**
* @see Table#has(java.lang.Object,
* java.lang.Object, boolean)
*/
@SuppressWarnings("unchecked")
public boolean has( Object key, Object val, boolean isGreaterThan ) throws NamingException
{
if ( !allowsDuplicates )
{
Object rval = getRaw( key );
// key does not exist so return nothing
if ( null == rval )
{
return false;
}
// val == val return true
else if ( val.equals( rval ) )
{
return true;
}
// val >= val and test is for greater then return true
else if ( comparator.compareValue( rval, val ) >= 1 && isGreaterThan )
{
return true;
}
// val <= val and test is for lesser then return true
else if ( comparator.compareValue( rval, val ) <= 1 && !isGreaterThan )
{
return true;
}
return false;
}
Object values = getRaw( key );
if ( values == null )
{
return false;
}
if ( values instanceof TreeSet )
{
TreeSet set = ( TreeSet ) values;
SortedSet subset = null;
if ( set.size() == 0 )
{
return false;
}
if ( isGreaterThan )
{
subset = set.tailSet( val );
}
else
{
subset = set.headSet( val );
}
if ( subset.size() > 0 || set.contains( val ) )
{
return true;
}
return false;
}
if ( values instanceof BTreeRedirect )
{
BTree tree = getBTree( ( BTreeRedirect ) values );
if ( tree.size() == 0 )
{
return false;
}
return btreeHas( tree, val, isGreaterThan );
}
throw new IllegalStateException( "When using duplicate keys either a TreeSet or BTree is used for values." );
}
/**
* @see Table#has(java.lang.Object, boolean)
*/
public boolean has( Object key, boolean isGreaterThan ) throws NamingException
{
try
{
// See if we can find the border between keys greater than and less
// than in the set of keys. This will be the spot we search from.
jdbm.helper.Tuple tuple = bt.findGreaterOrEqual( key );
// Test for equality first since it satisfies both greater/less than
if ( null != tuple && comparator.compareKey( tuple.getKey(), key ) == 0 )
{
return true;
}
// Greater searches are easy and quick thanks to findGreaterOrEqual
if ( isGreaterThan )
{
// A null return above means there were no equal or greater keys
if ( null == tuple )
{
return false;
}
// Not Null! - we found a tuple with equal or greater key value
return true;
}
// Less than searches occur below and are not as efficient or easy.
// We need to scan up from the begining if findGreaterOrEqual failed
// or scan down if findGreaterOrEqual succeed.
TupleBrowser browser = null;
if ( null == tuple )
{
// findGreaterOrEqual failed so we create a tuple and scan from
// the lowest values up via getNext comparing each key to key
tuple = new jdbm.helper.Tuple();
browser = bt.browse();
// We should at most have to read one key. If 1st key is not
// less than or equal to key then all keys are > key
// since the keys are assorted in ascending order based on the
// comparator.
while ( browser.getNext( tuple ) )
{
if ( comparator.compareKey( tuple.getKey(), key ) <= 0 )
{
return true;
}
return false;
}
}
else
{
// findGreaterOrEqual succeeded so use the existing tuple and
// scan the down from the highest key less than key via
// getPrevious while comparing each key to key.
browser = bt.browse( tuple.getKey() );
// The above call positions the browser just before the given
// key so we need to step forward once then back. Remember this
// key represents a key greater than or equal to key.
if ( comparator.compareKey( tuple.getKey(), key ) <= 0 )
{
return true;
}
browser.getNext( tuple );
// We should at most have to read one key, but we don't short
// the search as in the search above first because the chance of
// unneccessarily looping is nil since values get smaller.
while ( browser.getPrevious( tuple ) )
{
if ( comparator.compareKey( tuple.getKey(), key ) <= 0 )
{
return true;
}
}
}
}
catch ( IOException e )
{
NamingException ne = new NamingException();
ne.setRootCause( e );
throw ne;
}
return false;
}
/**
* @see org.apache.directory.server.core.partition.impl.btree.Table#has(java.lang.Object,
* java.lang.Object)
*/
public boolean has( Object key, Object value ) throws NamingException
{
if ( ! allowsDuplicates )
{
Object obj = getRaw( key );
if ( null == obj )
{
return false;
}
return obj.equals( value );
}
Object values = getRaw( key );
if ( values == null )
{
return false;
}
if ( values instanceof TreeSet )
{
return ( ( TreeSet ) values ).contains( value );
}
if ( values instanceof BTreeRedirect )
{
return btreeHas( getBTree( ( BTreeRedirect ) values ), value );
}
throw new IllegalStateException( "When using duplicate keys either a TreeSet or BTree is used for values." );
}
/**
* @see Table#has(java.lang.Object)
*/
public boolean has( Object key ) throws NamingException
{
return getRaw( key ) != null;
}
/**
* @see org.apache.directory.server.core.partition.impl.btree.Table#put(java.lang.Object,
* java.lang.Object)
*/
@SuppressWarnings("unchecked")
public Object put( Object key, Object value ) throws NamingException
{
Object replaced = null;
if ( ! allowsDuplicates )
{
replaced = putRaw( key, value, true );
if ( null == replaced )
{
count++;
}
return replaced;
}
Object values = getRaw( key );
if ( values == null )
{
values = new TreeSet( comparator.getValueComparator() );
}
if ( values instanceof TreeSet )
{
TreeSet set = ( TreeSet ) values;
if ( set.contains( value ) )
{
return value;
}
boolean addSuccessful = set.add( value );
if ( set.size() > numDupLimit )
{
BTree tree = convertToBTree( set );
BTreeRedirect redirect = new BTreeRedirect( tree.getRecid() );
replaced = putRaw( key, redirect, true );
}
else
{
replaced = putRaw( key, set, true );
}
if ( addSuccessful )
{
count++;
return replaced;
}
return null;
}
if ( values instanceof BTreeRedirect )
{
BTree tree = getBTree( ( BTreeRedirect ) values );
if ( insertDupIntoBTree( tree, value ) )
{
count++;
return values;
}
return null;
}
throw new IllegalStateException( "When using duplicate keys either a TreeSet or BTree is used for values." );
}
/**
* @see Table#put(java.lang.Object,
* javax.naming.NamingEnumeration)
*/
@SuppressWarnings("unchecked")
public Object put( Object key, NamingEnumeration values ) throws NamingException
{
/*
* If we do not allow duplicates call the single add put using the
* first value in the enumeration if it exists. If it does not we
* just return null without doing anything. If more than one value
* is in the enumeration than we blow a UnsupportedOperationException.
*/
if ( !allowsDuplicates )
{
if ( values.hasMore() )
{
Object value = values.next();
if ( values.hasMore() )
{
throw new UnsupportedOperationException( "Attempting to put duplicate keys into table " + name
+ " which does not support duplicates" );
}
return put( key, value );
}
return null;
}
Object storedValues = getRaw( key );
if ( storedValues == null )
{
storedValues = new TreeSet( comparator.getValueComparator() );
}
if ( storedValues instanceof TreeSet )
{
/*
* Here the table allows duplicates so we get the TreeSet from the
* Table holding all the duplicate key values or create one if it
* does not exist for key. We check if the value is present and
* if it is we add it and increment the table entry counter.
*/
TreeSet set = ( TreeSet ) storedValues;
while ( values.hasMore() )
{
Object val = values.next();
if ( !set.contains( val ) )
{
boolean isAddSuccessful = set.add( val );
if ( isAddSuccessful )
{
count++;
}
}
}
if ( set.size() > numDupLimit )
{
BTree tree = convertToBTree( set );
BTreeRedirect redirect = new BTreeRedirect( tree.getRecid() );
return putRaw( key, redirect, true );
}
else
{
return putRaw( key, set, true );
}
}
if ( storedValues instanceof BTreeRedirect )
{
BTree tree = getBTree( ( BTreeRedirect ) storedValues );
while ( values.hasMore() )
{
Object val = values.next();
if ( insertDupIntoBTree( tree, val ) )
{
count++;
}
}
return storedValues;
}
throw new IllegalStateException( "When using duplicate keys either a TreeSet or BTree is used for values." );
}
/**
* @see Table#remove(java.lang.Object,
* java.lang.Object)
*/
public Object remove( Object key, Object value ) throws NamingException
{
if ( ! allowsDuplicates )
{
Object oldValue = getRaw( key );
// Remove the value only if it is the same as value.
if ( oldValue != null && oldValue.equals( value ) )
{
removeRaw( key );
count--;
return oldValue;
}
return null;
}
Object values = getRaw( key );
if ( values == null )
{
return null;
}
if ( values instanceof TreeSet )
{
TreeSet set = ( TreeSet ) values;
// If removal succeeds then remove if set is empty else replace it
if ( set.remove( value ) )
{
if ( set.isEmpty() )
{
removeRaw( key );
}
else
{
putRaw( key, set, true );
}
// Decrement counter if removal occurs.
count--;
return value;
}
return null;
}
// TODO might be nice to add code here that reverts to a TreeSet
// if the number of duplicates falls below the numDupLimit value
if ( values instanceof BTreeRedirect )
{
BTree tree = getBTree( ( BTreeRedirect ) values );
if ( removeDupFromBTree( tree, value ) )
{
if ( tree.size() == 0 )
{
removeRaw( key );
}
count--;
return value;
}
return null;
}
throw new IllegalStateException( "When using duplicate keys either a TreeSet or BTree is used for values." );
}
/**
* @see Table#remove(java.lang.Object,
* javax.naming.NamingEnumeration)
*/
public Object remove( Object key, NamingEnumeration values ) throws NamingException
{
/*
* If we do not allow dupliicates call the single remove using the
* first value in the enumeration if it exists. If it does not we
* just return null without doing anything. If more than one value
* is in the enumeration than we blow a UnsupportedOperationException.
*/
if ( !allowsDuplicates )
{
if ( values.hasMore() )
{
Object value = values.next();
if ( values.hasMore() )
{
throw new UnsupportedOperationException( "Attempting to remove duplicate keys from table " + name
+ " which does not support duplicates" );
}
return remove( key, value );
}
return null;
}
Object storedValues = getRaw( key );
if ( storedValues == null )
{
return null;
}
if ( storedValues instanceof TreeSet )
{
/*
* Here the table allows duplicates so we get the TreeSet from the
* Table holding all the duplicate key values or return null if it
* does not exist for key - nothing to do here.
*/
TreeSet set = ( TreeSet ) storedValues;
/*
* So we have a valid TreeSet with values in it. We check if each value
* is in the set and remove it if it is present. We decrement the
* counter while doing so.
*/
Object firstValue = null;
while ( values.hasMore() )
{
Object val = values.next();
// get the first value
if ( firstValue == null )
{
firstValue = val;
}
if ( set.contains( val ) )
{
set.remove( val );
count--;
}
}
// Return the raw TreeSet and put the changed one back.
putRaw( key, set, true );
return firstValue;
}
// TODO might be nice to add code here that reverts to a TreeSet
// if the number of duplicates falls below the numDupLimit value
if ( storedValues instanceof BTreeRedirect )
{
BTree tree = getBTree( ( BTreeRedirect ) storedValues );
Object first = null;
while ( values.hasMore() )
{
Object val = values.next();
if ( removeDupFromBTree( tree, val ) )
{
count--;
if ( first == null )
{
first = val;
}
}
}
return first;
}
throw new IllegalStateException( "When using duplicate keys either a TreeSet or BTree is used for values." );
}
/**
* @see Table#remove(java.lang.Object)
*/
public Object remove( Object key ) throws NamingException
{
Object returned = removeRaw( key );
if ( null == returned )
{
return null;
}
if ( ! allowsDuplicates )
{
this.count--;
return returned;
}
if ( returned instanceof TreeSet )
{
TreeSet set = ( TreeSet ) returned;
this.count -= set.size();
return set.first();
}
if ( returned instanceof BTreeRedirect )
{
BTree tree = getBTree( ( BTreeRedirect ) returned );
this.count -= tree.size();
return removeAll( tree );
}
throw new IllegalStateException( "When using duplicate keys either a TreeSet or BTree is used for values." );
}
/**
* @see Table#listValues(java.lang.Object)
*/
public NamingEnumeration<Object> listValues( Object key ) throws NamingException
{
if ( !allowsDuplicates )
{
Object value = get( key );
if ( null == value )
{
return new EmptyEnumeration();
}
else
{
return new SingletonEnumeration( value );
}
}
Object values = getRaw( key );
if ( values == null )
{
return new EmptyEnumeration();
}
if ( values instanceof TreeSet )
{
TreeSet set = ( TreeSet ) values;
final Iterator list = set.iterator();
return new NamingEnumeration()
{
public void close()
{
}
public Object nextElement()
{
return list.next();
}
public Object next()
{
return list.next();
}
public boolean hasMore()
{
return list.hasNext();
}
public boolean hasMoreElements()
{
return list.hasNext();
}
};
}
if ( values instanceof BTreeRedirect )
{
BTree tree = getBTree( ( BTreeRedirect ) values );
return new BTreeEnumeration( tree );
}
throw new IllegalStateException( "When using duplicate keys either a TreeSet or BTree is used for values." );
}
// ------------------------------------------------------------------------
// listTuple Overloads
// ------------------------------------------------------------------------
/**
* @see org.apache.directory.server.core.partition.impl.btree.Table#listTuples()
*/
public NamingEnumeration<Tuple> listTuples() throws NamingException
{
NamingEnumeration<Tuple> list = null;
try
{
JdbmTupleBrowser browser = new JdbmTupleBrowser( bt.browse() );
list = new NoDupsEnumeration( browser, true );
}
catch ( IOException e )
{
NamingException ne = new NamingException();
ne.setRootCause( e );
throw ne;
}
if ( allowsDuplicates )
{
return new DupsEnumeration( this, ( NoDupsEnumeration ) list );
}
return list;
}
/**
* @see org.apache.directory.server.core.partition.impl.btree.Table#listTuples(java.lang.Object)
*/
@SuppressWarnings("unchecked")
public NamingEnumeration<Tuple> listTuples( Object key ) throws NamingException
{
// Handle single and zero value returns without duplicates enabled
if ( !allowsDuplicates )
{
Object val = getRaw( key );
if ( null == val )
{
return new EmptyEnumeration();
}
else
{
return new SingletonEnumeration( new Tuple( key, getRaw( key ) ) );
}
}
Object values = getRaw( key );
if ( values == null )
{
return new EmptyEnumeration();
}
if ( values instanceof TreeSet )
{
TreeSet set = ( TreeSet ) values;
Object[] objs = new Object[set.size()];
objs = set.toArray( objs );
ArrayIterator iterator = new ArrayIterator( objs );
return new TupleEnumeration( key, iterator );
}
if ( values instanceof BTreeRedirect )
{
return new BTreeTupleEnumeration( getBTree( ( BTreeRedirect ) values ), key );
}
throw new IllegalStateException( "When using duplicate keys either a TreeSet or BTree is used for values." );
}
/**
* @see Table#listTuples(java.lang.Object,
* boolean)
*/
public NamingEnumeration<Tuple> listTuples( Object key, boolean isGreaterThan ) throws NamingException
{
NamingEnumeration<Tuple> list = null;
try
{
if ( isGreaterThan )
{
JdbmTupleBrowser browser = new JdbmTupleBrowser( bt.browse( key ) );
list = new NoDupsEnumeration( browser, isGreaterThan );
}
else
{
/* According to the jdbm docs a browser is positioned right
* before a key greater than or equal to key. getNext() will
* return the next tuple with a key greater than or equal to
* key. getPrevious() used in descending scans for less than
* for equal to comparisions will not. We need to advance
* forward once and check if the returned Tuple key equals
* key. If it does then we do nothing feeding in the browser
* to the NoDupsCursor. If it does not we call getPrevious and
* pass it into the NoDupsCursor constructor.
*/
jdbm.helper.Tuple tuple = new jdbm.helper.Tuple();
TupleBrowser browser = bt.browse( key );
if ( browser.getNext( tuple ) )
{
Object greaterKey = tuple.getKey();
if ( 0 != comparator.compareKey( key, greaterKey ) )
{
// Make sure we don't return greaterKey in cursor
browser.getPrevious( tuple );
}
}
// If greaterKey != key above then it will not be returned.
list = new NoDupsEnumeration( new JdbmTupleBrowser( browser ), isGreaterThan );
}
}
catch ( IOException e )
{
NamingException ne = new NamingException( "Failed to get TupleBrowser on table " + name + " using key "
+ renderKey( key ) );
ne.setRootCause( e );
throw ne;
}
if ( allowsDuplicates )
{
list = new DupsEnumeration( this, ( NoDupsEnumeration ) list );
}
return list;
}
/**
* @see org.apache.directory.server.core.partition.impl.btree.Table#listTuples(java.lang.Object,
* java.lang.Object, boolean)
*/
@SuppressWarnings("unchecked")
public NamingEnumeration listTuples( Object key, Object val, boolean isGreaterThan ) throws NamingException
{
if ( !allowsDuplicates )
{
throw new UnsupportedOperationException( "Cannot list tuples over duplicates on table that " +
"does not support duplicates." );
// Object rval = getRaw( key );
//
// if ( null == rval ) // key does not exist so return nothing
// {
// return new EmptyEnumeration();
// }
// else if ( val.equals( rval ) ) // val == rval return tuple
// {
// return new SingletonEnumeration( new Tuple( key, val ) );
// }
// // val >= val and test is for greater then return tuple
// else if ( comparator.compareValue( val, rval ) >= 1 && isGreaterThan )
// {
// return new SingletonEnumeration( new Tuple( key, val ) );
// }
// // val <= val and test is for lesser then return tuple
// else if ( comparator.compareValue( val, rval ) <= 1 && !isGreaterThan )
// {
// return new SingletonEnumeration( new Tuple( key, val ) );
// }
//
// return new EmptyEnumeration();
}
Object values = getRaw( key );
if ( values == null )
{
return new EmptyEnumeration();
}
if ( values instanceof TreeSet )
{
TreeSet set = ( TreeSet ) values;
if ( isGreaterThan )
{
Set tailSet = set.tailSet( val );
if ( tailSet.isEmpty() )
{
return new EmptyEnumeration();
}
Object[] objs = new Object[tailSet.size()];
objs = tailSet.toArray( objs );
ArrayIterator iterator = new ArrayIterator( objs );
return new TupleEnumeration( key, iterator );
}
else
{
// Get all values from the smallest upto val and put them into
// a list. They will be in ascending order so we need to reverse
// the list after adding val which is not included in headSet.
SortedSet headset = set.headSet( val );
List list = new ArrayList( headset.size() + 1 );
list.addAll( headset );
// Add largest value (val) if it is in the set. TreeSet.headSet
// does not get val if val is in the set. So we add it now to
// the end of the list. List is now ascending from smallest to
// val
if ( set.contains( val ) )
{
list.add( val );
}
// Reverse the list now we have descending values from val to the
// smallest value that key has. Return tuple cursor over list.
Collections.reverse( list );
return new TupleEnumeration( key, list.iterator() );
}
}
if ( values instanceof BTreeRedirect )
{
return new BTreeTupleEnumeration( getBTree( ( BTreeRedirect ) values ),
comparator.getValueComparator(), key, val, isGreaterThan );
}
throw new IllegalStateException( "When using duplicate keys either a TreeSet or BTree is used for values." );
}
// ------------------------------------------------------------------------
// Maintenance Operations
// ------------------------------------------------------------------------
/**
* @see Table#close()
*/
public synchronized void close() throws NamingException
{
sync();
}
/**
* Synchronizes the buffers with disk.
*
* @throws NamingException if errors are encountered on the flush
*/
public void sync() throws NamingException
{
try
{
long recId = recMan.getNamedObject( name + SZSUFFIX );
if ( 0 == recId )
{
recId = recMan.insert( new Integer( count ) );
}
else
{
recMan.update( recId, new Integer( count ) );
}
}
catch ( IOException e )
{
NamingException ne = new NamingException();
ne.setRootCause( e );
throw ne;
}
}
// ------------------------------------------------------------------------
// Private Utility Methods
// ------------------------------------------------------------------------
/**
* Renders a key using the renderer associated with this table.
*
* @param obj the key to render.
* @return the rendered String representation of obj
*/
private String renderKey( Object obj )
{
StringBuffer buf = new StringBuffer();
buf.append( "\'" );
if ( null == renderer )
{
buf.append( obj.toString() );
}
else
{
buf.append( renderer.getKeyString( obj ) );
}
buf.append( "\'" );
return buf.toString();
}
/**
* Gets a Tuple value from the btree while wrapping any IOExceptions with a
* NamingException.
*
* @param key the key of the Tuple to get the value of
* @return the raw value object from the btree
* @throws NamingException if there are any problems accessing the btree.
*/
private Object getRaw( Object key ) throws NamingException
{
Object val = null;
if ( null == key )
{
return null;
}
try
{
if ( !allowsDuplicates )
{
val = bt.find( key );
}
else
{
val = bt.find( key );
}
}
catch ( IOException e )
{
NamingException ne = new NamingException();
ne.setRootCause( e );
throw ne;
}
return val;
}
/**
* Puts a Tuple into the btree while wrapping any IOExceptions with a
* NamingException.
*
* @param key the key of the Tuple to put
* @param value the value of the Tuple to put
* @param doReplace whether or not to replace the object if it exists
* @return the raw value object removed from the btree on replacement
* @throws NamingException if there are any problems accessing the btree.
*/
private Object putRaw( Object key, Object value, boolean doReplace ) throws NamingException
{
Object val = null;
try
{
val = bt.insert( key, value, doReplace );
}
catch ( IOException e )
{
NamingException ne = new NamingException();
ne.setRootCause( e );
throw ne;
}
return val;
}
/**
* Removes a entry from the btree while wrapping any IOExceptions with a
* NamingException.
*
* @param key the key of the Tuple to remove
* @return the raw value object removed from the btree
* @throws NamingException if there are any problems accessing the btree.
*/
private Object removeRaw( Object key ) throws NamingException
{
Object val = null;
try
{
val = bt.remove( key );
}
catch ( IOException e )
{
NamingException ne = new NamingException();
ne.setRootCause( e );
throw ne;
}
return val;
}
BTree getBTree( BTreeRedirect redirect ) throws NamingException
{
if ( duplicateBtrees.containsKey( redirect.getRecId() ) )
{
return duplicateBtrees.get( redirect.getRecId() );
}
try
{
BTree tree = BTree.load( recMan, redirect.getRecId().longValue() );
duplicateBtrees.put( redirect.getRecId(), tree );
return tree;
}
catch ( IOException e )
{
LdapNamingException lne = new LdapNamingException( "Failed to load btree",
ResultCodeEnum.OTHER );
lne.setRootCause( e );
throw lne;
}
}
private Object firstKey ( BTree tree ) throws NamingException
{
jdbm.helper.Tuple tuple = new jdbm.helper.Tuple();
boolean success = false;
try
{
success = tree.browse().getNext( tuple );
if ( success )
{
return tuple.getKey();
}
else
{
return null;
}
}
catch ( IOException e )
{
LdapNamingException lne = new LdapNamingException( "IO failure while acessing btree: "
+ e.getMessage(), ResultCodeEnum.OTHER );
lne.setRootCause( e );
throw lne;
}
}
private boolean btreeHas( BTree tree, Object key, boolean isGreaterThan ) throws NamingException
{
jdbm.helper.Tuple tuple = new jdbm.helper.Tuple();
try
{
TupleBrowser browser = tree.browse( key );
if ( isGreaterThan )
{
boolean success = browser.getNext( tuple );
if ( success )
{
return true;
}
else
{
return false;
}
}
else
{
boolean success = browser.getPrevious( tuple );
if ( success )
{
return true;
}
else
{
/*
* Calls to getPrevious() will return a lower key even
* if there exists a key equal to the one searched
* for. Since isGreaterThan when false really means
* 'less than or equal to' we must check to see if
* the key in front is equal to the key argument provided.
*/
success = browser.getNext( tuple );
if ( success )
{
Object biggerKey = tuple.getKey();
if ( comparator.compareValue( key, biggerKey ) == 0 )
{
return true;
}
}
return false;
}
}
}
catch ( IOException e )
{
LdapNamingException lne = new LdapNamingException( "IO failure while acessing btree: "
+ e.getMessage(), ResultCodeEnum.OTHER );
lne.setRootCause( e );
throw lne;
}
}
private boolean btreeHas( BTree tree, Object key ) throws NamingException
{
jdbm.helper.Tuple tuple = new jdbm.helper.Tuple();
try
{
TupleBrowser browser = tree.browse( key );
boolean success = browser.getNext( tuple );
if ( success )
{
if ( comparator.compareValue( key, tuple.getKey() ) == 0 )
{
return true;
}
}
return false;
}
catch ( IOException e )
{
LdapNamingException lne = new LdapNamingException( "IO failure while acessing btree: "
+ e.getMessage(), ResultCodeEnum.OTHER );
lne.setRootCause( e );
throw lne;
}
}
private boolean insertDupIntoBTree( BTree tree, Object value ) throws LdapNamingException
{
try
{
Object replaced = tree.insert( value, EMPTY_BYTES, true );
return null == replaced;
}
catch ( IOException e )
{
LdapNamingException lne = new LdapNamingException( "Failed to insert dup into BTree",
ResultCodeEnum.OTHER );
lne.setRootCause( e );
throw lne;
}
}
private boolean removeDupFromBTree( BTree tree, Object value ) throws LdapNamingException
{
try
{
Object removed = null;
if ( tree.find( value ) != null )
{
removed = tree.remove( value );
}
return null != removed;
}
catch ( IOException e )
{
LdapNamingException lne = new LdapNamingException( "Failed to remove dup from BTree",
ResultCodeEnum.OTHER );
lne.setRootCause( e );
throw lne;
}
}
private static final byte[] EMPTY_BYTES = new byte[0];
private BTree convertToBTree( TreeSet set ) throws NamingException
{
try
{
BTree tree = BTree.createInstance( recMan, comparator.getValueComparator() );
for ( Iterator ii = set.iterator(); ii.hasNext(); /**/ )
{
tree.insert( ii.next(), EMPTY_BYTES, true );
}
return tree;
}
catch ( IOException e )
{
LdapNamingException lne = new LdapNamingException( "Failed to convert TreeSet values to BTree",
ResultCodeEnum.OTHER );
lne.setRootCause( e );
throw lne;
}
}
private Object removeAll( BTree tree ) throws NamingException
{
Object first = null;
jdbm.helper.Tuple jdbmTuple = new jdbm.helper.Tuple();
TupleBrowser browser;
try
{
browser = tree.browse();
while( browser.getNext( jdbmTuple ) )
{
tree.remove( jdbmTuple.getKey() );
if ( first == null )
{
first = jdbmTuple.getKey();
}
}
}
catch ( IOException e )
{
LdapNamingException lne = new LdapNamingException( "Failed to remove all keys in BTree",
ResultCodeEnum.OTHER );
lne.setRootCause( e );
throw lne;
}
return first;
}
}