/* * 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.pluggable; import java.io.ByteArrayOutputStream; import org.forgerock.opendj.ldap.ByteSequence; /** * 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. *
*/ final class IndexOutputBuffer implements ComparableTrue if key is an insert, false otherwise.
*/
public void add(ByteSequence 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
keyOffset = writeInt(buffer, keyOffset, recordOffset);
bytesLeft = recordOffset - keyOffset;
keys++;
}
/**
* Writes the full record minus the record size itself.
*/
private int addRecord(ByteSequence key, long entryID, int indexID, boolean insert)
{
int retOffset = recordOffset - getRecordSize(key.length());
int offSet = retOffset;
// write the INS/DEL bit
buffer[offSet++] = insert ? INS : DEL;
// write the indexID
offSet = writeInt(buffer, offSet, indexID);
// write the entryID
offSet = writeLong(buffer, offSet, entryID);
// write the key length
offSet = writeInt(buffer, offSet, key.length());
// write the key bytes
key.copyTo(buffer, offSet);
return retOffset;
}
/**
* Computes the full size of the record.
*
* @param keyLen The length of the key of index
* @param entryID The entry id
* @return The size that such record would take in the IndexOutputBuffer
*/
public static int getRequiredSize(int keyLen, long entryID)
{
// also add up the space needed to store the record size
return getRecordSize(keyLen) + INT_SIZE;
}
private static int getRecordSize(int keyLen)
{
// Adds up (INS/DEL bit + indexID) + entryID + key length + key bytes
return REC_OVERHEAD + LONG_SIZE + INT_SIZE + keyLen;
}
/**
* Write the entryID at the specified position to the specified output stream.
* Used when writing the index scratch files.
*
* @param stream The stream to write the record at the index to.
* @param position The position of the record to write.
*/
public void writeEntryID(ByteArrayOutputStream stream, int position)
{
int offSet = getOffset(position);
stream.write(buffer, offSet + REC_OVERHEAD, LONG_SIZE);
}
/**
* Return {@code true} if the record specified by the position is an insert
* record, or {@code false} if it a delete record.
*
* @param position The position 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 position)
{
int recOffset = getOffset(position);
return buffer[recOffset] == INS;
}
private int getOffset(int position)
{
return readInt(position * INT_SIZE);
}
private int compare(int xPosition, int yPosition)
{
return toRecord(xPosition).compareTo(toRecord(yPosition));
}
private ImportRecord toRecord(int position)
{
return ImportRecord.fromBufferAndPosition(buffer, position);
}
ImportRecord currentRecord()
{
return currentRecord;
}
/**
* 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)
{
int cmp = currentRecord().compareTo(b.currentRecord());
if (cmp == 0)
{
// This is tested in a tree set remove when a buffer is removed from the tree set.
return compareLongs(bufferID, b.getBufferID());
}
return cmp;
}
private static int compareLongs(long l1, long l2)
{
if (l1 == l2)
{
return 0;
}
else if (l1 < l2)
{
return -1;
}
else
{
return 1;
}
}
/**
* Compare the byte array at the current position with the byte array at the
* provided position.
*
* @param position The index pointing to the byte array to compare.
* @return {@code true} if the byte arrays are equal, or {@code false} otherwise
*/
public boolean sameKeyAndIndexID(int position)
{
return currentRecord().equals(toRecord(position));
}
/**
* 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()
{
setPosition(position + 1);
}
private int writeInt(byte[] buffer, int offset, int val)
{
final int endOffset = offset + INT_SIZE;
for (int i = endOffset - 1; i >= offset; i--) {
buffer[i] = (byte) (val & 0xff);
val >>>= 8;
}
return endOffset;
}
private int writeLong(byte[] buffer, int offset, long val)
{
final int endOffset = offset + LONG_SIZE;
for (int i = endOffset - 1; i >= offset; i--) {
buffer[i] = (byte) (val & 0xff);
val >>>= 8;
}
return endOffset;
}
private int readInt(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)
{
ImportRecord ra = toRecord(a);
ImportRecord rb = toRecord(b);
ImportRecord rc = toRecord(c);
return ra.compareTo(rb) < 0
? (rb.compareTo(rc) < 0 ? b : ra.compareTo(rc) < 0 ? c : a)
: (rb.compareTo(rc) > 0 ? b : ra.compareTo(rc) > 0 ? c : a);
}
private void sort(int off, int len)
{
if (len < 7) {
for (int i=off; i