/*
* 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.jena.tdb.base.buffer;
import static java.lang.String.format;
import static org.apache.jena.atlas.lib.Alg.encodeIndex ;
import java.nio.ByteBuffer;
import java.util.Iterator;
import org.apache.jena.tdb.base.record.Record ;
import org.apache.jena.tdb.base.record.RecordFactory ;
final
public class RecordBuffer extends BufferBase
{
private RecordFactory factory ;
// Need own specialized binary search :-(
public RecordBuffer(RecordFactory recFactory, int maxRec)
{
this(ByteBuffer.allocate(recFactory.recordLength()*maxRec), recFactory, 0) ;
}
public RecordBuffer(ByteBuffer bb, RecordFactory recFactory, int num)
{
super(bb, recFactory.recordLength(), num) ;
this.factory = recFactory ;
}
public Record get(int idx)
{
checkBounds(idx, numSlot) ;
return _get(idx) ;
}
public Record getLow()
{
if ( numSlot == 0 )
throw new IllegalArgumentException("getLow: Empty RecordBuffer") ;
return _get(0) ;
}
public Record getHigh()
{
if ( numSlot == 0 )
throw new IllegalArgumentException("getHigh: Empty RecordBuffer") ;
return _get(numSlot-1) ;
}
// Inserts at top.
public void add(Record record) { add(numSlot, record) ; }
// Inserts at slot idx
public void add(int idx, Record record)
{
if ( idx != numSlot )
{
checkBounds(idx, numSlot) ;
shiftUp(idx) ; // Changes count.
}
else
{
if ( numSlot >= maxSlot )
throw new BufferException(format("Out of bounds: idx=%d, ptrs=%d", idx, maxSlot)) ;
numSlot++ ;
}
_set(idx, record) ;
}
// Overwrites the contents of slot idx
public void set(int idx, Record record)
{
if ( idx == numSlot )
{
add(idx, record) ;
return ;
}
else
checkBounds(idx, numSlot) ;
_set(idx, record) ;
}
// No checking bound : careful use only!
public Record _get(int idx)
{
return factory.buildFrom(bb, idx) ;
}
// No bounds checking : careful use only!
void _set(int idx, Record rec)
{
factory.insertInto(rec, bb, idx) ;
}
// Linear search for testing.
int find1(byte[] data)
{
for ( int i = 0 ; i < numSlot ; i++ )
{
int x = compare(i, data) ;
if ( x == 0 )
return i ;
if ( x > 0 )
return encodeIndex(i) ;
}
return encodeIndex(numSlot) ;
}
// Binary search
public int find(Record k)
{
return find(k, 0, numSlot) ;
}
public Iterator<Record> iterator() { return new RecordBufferIterator(this) ; }
/** Iterator over a range from min (inclusive) to max(exclusive) */
public Iterator<Record> iterator(Record min, Record max) { return new RecordBufferIterator(this, min, max) ; }
public Record findGet(Record k)
{
int x = find(k) ;
if ( x >= 0 )
return get(x) ;
return null ;
}
/** return true is removed anything */
public boolean removeByKey(Record k)
{
int x = find(k) ;
if ( x < 0 )
return false ;
super.remove(x) ;
return true ;
}
/** Search for key in range fromIndex (inclusive) to toIndex (exclusive) */
public int find(Record rec, int fromIndex, int toIndex)
{
int low = fromIndex ;
int high = toIndex-1 ;
byte[] key = rec.getKey() ;
// http://en.wikipedia.org/wiki/Binary_search
while (low <= high)
{
int mid = (low + high) >>> 1 ; // int divide by 2
int x = compare(mid, key) ;
//System.out.printf("Compare: %d(%s) %s ==> %d\n", mid, Record.str(get(mid)), Record.str(data), x) ;
if ( x < 0 )
low = mid + 1 ;
else if ( x > 0 )
high = mid - 1 ;
else
return mid ;
}
// On exit, when not finding, low is the least value
// above, including off the end of the array.
return encodeIndex(low) ;
}
// Record compareByKey except we avoid touching bytes by exiting as soon as possible.
// No record created as would be by using compareByKey(RecordBuffer.get(idx), record)
// Compare the slot at idx with value.
private int compare(int idx, byte[] value)
{
idx = idx*slotLen ;
for ( int i = 0 ; i < value.length ; i++ )
{
byte b1 = bb.get(idx+i) ;
byte b2 = value[i] ;
if ( b1 == b2 )
continue ;
return (b1&0xFF) - (b2&0xFF) ;
}
return 0 ;
}
@Override
public String toString()
{
StringBuilder str = new StringBuilder(40000) ;
str.append(format("Len=%d Max=%d: ", numSlot, bb.limit()/slotLen)) ;
// Print active slots as records.
for ( int i = 0 ; i < numSlot ; i++ )
{
if ( i != 0 )
str.append(" ") ;
Record r = _get(i) ;
str.append(r.toString()) ;
}
// // Print empty slots
// for ( int i = numSlot*slotLen ; i < maxSlot*slotLen ; i++ )
// {
// if ( i != 0 && i%slotLen == 0 )
// str.append(" ") ;
// byte b = bb.get(i) ;
// str.append(format("%02x", b)) ;
// }
String s = str.toString() ;
return s ;
}
private static void checkBounds(int idx, int len)
{
if ( idx < 0 || idx >= len )
throw new IllegalArgumentException(format("Out of bounds: idx=%d, size=%d", idx, len)) ;
}
/** A duplicate which does not share anything with the original - for testing */
public RecordBuffer duplicate()
{
RecordBuffer n = new RecordBuffer(factory, maxSlot) ;
copy(0, n, 0, maxSlot) ; // numSlot
n.numSlot = numSlot ; // reset the allocated length
return n ;
}
}