/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2009-2010 Sun Microsystems, Inc. * Portions Copyright 2013-2015 ForgeRock AS. */ package org.opends.server.backends.jeb; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import com.sleepycat.util.PackedInteger; /** * This class represents a index buffer used to store the keys and entry IDs * processed from the LDIF file during phase one of an import, or rebuild index * process. Each key and ID is stored in a record in the buffer. *
* The records in the buffer are eventually sorted, based on the key, when the * maximum size of the buffer is reached and no more records will fit into the * buffer. The buffer is scheduled to be flushed to an index scratch file and * then re-cycled by the import, or rebuild-index process. *
** The structure of a record in the buffer is the following: * *
* +-------------+-------------+---------+---------+------------+-----------+ * | record size | INS/DEL bit | indexID | entryID | key length | key bytes | * +-------------+-------------+---------+---------+------------+-----------+ ** * The record size is used for fast seeks to quickly "jump" over records. * *
* The records are packed as much as possible, to optimize the buffer space.
* This class is not thread safe.
*
True if key is an insert, false otherwise.
*/
public void add(byte[] keyBytes, EntryID entryID, int indexID,
boolean insert) {
// write the record data, but leave the space to write the record size just
// before it
recordOffset = addRecord(keyBytes, entryID.longValue(), indexID, insert);
// then write the returned record size
System.arraycopy(getIntBytes(recordOffset), 0, buffer, keyOffset, INT_SIZE);
keyOffset += INT_SIZE;
bytesLeft = recordOffset - keyOffset;
keys++;
}
/**
* Writes the full record minus the record size itself.
*/
private int addRecord(byte[]key, long id, int indexID, boolean insert)
{
int retOffset = recordOffset - getRecordSize(key.length, id);
int offSet = retOffset;
// write the INS/DEL bit
buffer[offSet++] = insert ? INS : DEL;
// write the indexID
System.arraycopy(getIntBytes(indexID), 0, buffer, offSet, INT_SIZE);
offSet += INT_SIZE;
// write the entryID
offSet = PackedInteger.writeLong(buffer, offSet, id);
// write the key length
offSet = PackedInteger.writeInt(buffer, offSet, key.length);
// write the key bytes
System.arraycopy(key, 0, buffer, offSet, key.length);
return retOffset;
}
/**
* Computes the full size of the record.
*
* @param keyLen The length of the key of index
* @param id The entry id
* @return The size that such record would take in the IndexOutputBuffer
*/
public static int getRequiredSize(int keyLen, long id)
{
// Adds up the key length + key bytes + entryID + indexID + the INS/DEL bit
// and finally the space needed to store the record size
return getRecordSize(keyLen, id) + INT_SIZE;
}
private static int getRecordSize(int keyLen, long id)
{
// Adds up the key length + key bytes + ...
return PackedInteger.getWriteIntLength(keyLen) + keyLen +
// ... entryID + (indexID + INS/DEL bit).
PackedInteger.getWriteLongLength(id) + REC_OVERHEAD;
}
/**
* Write record at specified index to the specified output stream. Used when
* when writing the index scratch files.
* @param stream The stream to write the record at the index to.
* @param index The index of the record to write.
*/
public void writeID(ByteArrayOutputStream stream, int index)
{
int offSet = getIntegerValue(index * INT_SIZE);
int len = PackedInteger.getReadLongLength(buffer, offSet + REC_OVERHEAD);
stream.write(buffer, offSet + REC_OVERHEAD, len);
}
/**
* Return {@code true} if the record specified by the index is an insert
* record, or {@code false} if it a delete record.
*
* @param index The index of the record.
*
* @return {@code true} if the record is an insert record, or {@code false}
* if it is a delete record.
*/
public boolean isInsertRecord(int index)
{
int recOffset = getIntegerValue(index * INT_SIZE);
return buffer[recOffset] != DEL;
}
/**
* Return the size of the key part of the record.
*
* @return The size of the key part of the record.
*/
public int getKeySize()
{
int offSet = getIntegerValue(position * INT_SIZE) + REC_OVERHEAD;
offSet += PackedInteger.getReadIntLength(buffer, offSet);
return PackedInteger.readInt(buffer, offSet);
}
/**
* Return the key value part of a record indicated by the current buffer
* position.
*
* @return byte array containing the key value.
*/
public byte[] getKey()
{
return getKey(position);
}
/** Used to minimized memory usage when comparing keys. */
private ByteBuffer getKeyBuf(int x)
{
keyBuffer.clear();
int offSet = getIntegerValue(x * INT_SIZE) + REC_OVERHEAD;
offSet += PackedInteger.getReadIntLength(buffer, offSet);
int keyLen = PackedInteger.readInt(buffer, offSet);
offSet += PackedInteger.getReadIntLength(buffer, offSet);
//Re-allocate if the key is bigger than the capacity.
if(keyLen > keyBuffer.capacity())
{
keyBuffer = ByteBuffer.allocate(keyLen);
}
keyBuffer.put(buffer, offSet, keyLen);
keyBuffer.flip();
return keyBuffer;
}
/**
* Return the key value part of a record specified by the index.
*
* @param x index to return.
* @return byte array containing the key value.
*/
private byte[] getKey(int x)
{
int offSet = getIntegerValue(x * INT_SIZE) + REC_OVERHEAD;
offSet += PackedInteger.getReadIntLength(buffer, offSet);
int keyLen = PackedInteger.readInt(buffer, offSet);
offSet += PackedInteger.getReadIntLength(buffer, offSet);
byte[] key = new byte[keyLen];
System.arraycopy(buffer, offSet, key, 0, keyLen);
return key;
}
private int getIndexID(int x)
{
return getIntegerValue(getIntegerValue(x * INT_SIZE) + 1);
}
/**
* Return index id associated with the current position's record.
*
* @return The index id.
*/
public int getIndexID()
{
return getIntegerValue(getIntegerValue(position * INT_SIZE) + 1);
}
private boolean is(int x, int y, CompareOp op)
{
int xoffSet = getIntegerValue(x * INT_SIZE);
int xIndexID = getIntegerValue(xoffSet + 1);
xoffSet += REC_OVERHEAD;
xoffSet += PackedInteger.getReadIntLength(buffer, xoffSet);
int xKeyLen = PackedInteger.readInt(buffer, xoffSet);
int xKey = PackedInteger.getReadIntLength(buffer, xoffSet) + xoffSet;
int yoffSet = getIntegerValue(y * INT_SIZE);
int yIndexID = getIntegerValue(yoffSet + 1);
yoffSet += REC_OVERHEAD;
yoffSet += PackedInteger.getReadIntLength(buffer, yoffSet);
int yKeyLen = PackedInteger.readInt(buffer, yoffSet);
int yKey = PackedInteger.getReadIntLength(buffer, yoffSet) + yoffSet;
return evaluateReturnCode(comparator.compare(buffer, xKey, xKeyLen,
xIndexID, yKey, yKeyLen, yIndexID), op);
}
private boolean is(int x, byte[] yKey, CompareOp op, int yIndexID)
{
int xoffSet = getIntegerValue(x * INT_SIZE);
int xIndexID = getIntegerValue(xoffSet + 1);
xoffSet += REC_OVERHEAD;
xoffSet += PackedInteger.getReadIntLength(buffer, xoffSet);
int xKeyLen = PackedInteger.readInt(buffer, xoffSet);
int xKey = PackedInteger.getReadIntLength(buffer, xoffSet) + xoffSet;
return evaluateReturnCode(comparator.compare(buffer, xKey, xKeyLen,
xIndexID, yKey, yKey.length, yIndexID), op);
}
/**
* Compare the byte array at the current position with the specified one and
* using the specified index id. It will return {@code true} if the byte
* array at the current position is equal to the specified byte array as
* determined by the comparator and the index ID is is equal. It will
* return {@code false} otherwise.
*
* @param b The byte array to compare.
* @param bIndexID The index key.
* @return True if the byte arrays are equal.
*/
public boolean compare(byte[]b, int bIndexID)
{
int offset = getIntegerValue(position * INT_SIZE);
int indexID = getIntegerValue(offset + 1);
offset += REC_OVERHEAD;
offset += PackedInteger.getReadIntLength(buffer, offset);
int keyLen = PackedInteger.readInt(buffer, offset);
int key = PackedInteger.getReadIntLength(buffer, offset) + offset;
return comparator.compare(buffer, key, keyLen, b, b.length) == 0
&& indexID == bIndexID;
}
/**
* Compare current IndexBuffer to the specified index buffer using both the
* comparator and index ID of both buffers.
*
* The key at the value of position in both buffers are used in the compare.
*
* @param b The IndexBuffer to compare to.
* @return 0 if the buffers are equal, -1 if the current buffer is less
* than the specified buffer, or 1 if it is greater.
*/
@Override
public int compareTo(IndexOutputBuffer b)
{
final ByteBuffer keyBuf = b.getKeyBuf(b.position);
int offset = getIntegerValue(position * INT_SIZE);
int indexID = getIntegerValue(offset + 1);
offset += REC_OVERHEAD;
offset += PackedInteger.getReadIntLength(buffer, offset);
int keyLen = PackedInteger.readInt(buffer, offset);
int key = PackedInteger.getReadIntLength(buffer, offset) + offset;
final int cmp = comparator.compare(buffer, key, keyLen, keyBuf.array(), keyBuf.limit());
if (cmp != 0)
{
return cmp;
}
final int bIndexID = b.getIndexID();
if (indexID == bIndexID)
{
// This is tested in a tree set remove when a buffer is removed from the tree set.
return compare(this.id, b.getBufferID());
}
else if (indexID < bIndexID)
{
return -1;
}
else
{
return 1;
}
}
private int compare(long l1, long l2)
{
if (l1 == l2)
{
return 0;
}
else if (l1 < l2)
{
return -1;
}
else
{
return 1;
}
}
/**
* Write a record to specified output stream using the record pointed to by
* the current position and the specified byte stream of ids.
*
* @param dataStream The data output stream to write to.
*
* @throws IOException If an I/O error occurs writing the record.
*/
public void writeKey(DataOutputStream dataStream) throws IOException
{
int offSet = getIntegerValue(position * INT_SIZE) + REC_OVERHEAD;
offSet += PackedInteger.getReadIntLength(buffer, offSet);
int keyLen = PackedInteger.readInt(buffer, offSet);
offSet += PackedInteger.getReadIntLength(buffer, offSet);
dataStream.write(buffer, offSet, keyLen);
}
/**
* Compare the byte array at the current position with the byte array at the
* specified index.
*
* @param i The index pointing to the byte array to compare.
* @return {@code true} if the byte arrays are equal, or {@code false} otherwise
*/
public boolean compare(int i)
{
return is(i, position, CompareOp.EQ);
}
/**
* Return the current number of keys.
*
* @return The number of keys currently in an index buffer.
*/
public int getNumberKeys()
{
return keys;
}
/**
* Return {@code true} if the buffer has more data to process, or
* {@code false} otherwise. Used when iterating over the buffer writing the
* scratch index file.
*
* @return {@code true} if a buffer has more data to process, or
* {@code false} otherwise.
*/
public boolean hasMoreData()
{
return position + 1 < keys;
}
/**
* Advance the position pointer to the next record in the buffer. Used when
* iterating over the buffer examining keys.
*/
public void nextRecord()
{
position++;
}
private byte[] getIntBytes(int val)
{
for (int i = 3; i >= 0; i--) {
intBytes[i] = (byte) (val & 0xff);
val >>>= 8;
}
return intBytes;
}
private int getIntegerValue(int index)
{
int answer = 0;
for (int i = 0; i < INT_SIZE; i++) {
byte b = buffer[index + i];
answer <<= 8;
answer |= (b & 0xff);
}
return answer;
}
private int med3(int a, int b, int c)
{
return is(a, b, CompareOp.LT) ?
(is(b,c,CompareOp.LT) ? b : is(a,c,CompareOp.LT) ? c : a) :
(is(b,c,CompareOp.GT) ? b : is(a,c,CompareOp.GT) ? c : a);
}
private void sort(int off, int len)
{
if (len < 7) {
for (int i=off; i