/* * 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 static org.opends.server.backends.pluggable.Importer.indexComparator; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import org.forgerock.opendj.ldap.ByteSequence; import org.forgerock.opendj.ldap.ByteStringBuilder; /** * 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;
}
/**
* 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 = getOffset(position) + REC_OVERHEAD + LONG_SIZE;
return 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 ByteStringBuilder getKeyBuf(int position)
{
keyBuffer.clear();
int offSet = getOffset(position) + REC_OVERHEAD + LONG_SIZE;
int keyLen = readInt(buffer, offSet);
offSet += INT_SIZE;
keyBuffer.append(buffer, offSet, keyLen);
return keyBuffer;
}
/**
* Return the key value part of a record specified by the index.
*
* @param position position to return.
* @return byte array containing the key value.
*/
private byte[] getKey(int position)
{
int offSet = getOffset(position) + REC_OVERHEAD + LONG_SIZE;
int keyLen = readInt(buffer, offSet);
offSet += INT_SIZE;
byte[] key = new byte[keyLen];
System.arraycopy(buffer, offSet, key, 0, keyLen);
return key;
}
private int getOffset(int position)
{
return readInt(position * INT_SIZE);
}
/**
* Return index id associated with the current position's record.
*
* @return The index id.
*/
public int getIndexID()
{
return getIndexID(position);
}
private int getIndexID(int position)
{
return getIndexIDFromOffset(getOffset(position));
}
private int getIndexIDFromOffset(int offset)
{
return readInt(offset + 1);
}
private int compare(int xPosition, int yPosition)
{
int xoffSet = getOffset(xPosition);
int xIndexID = getIndexIDFromOffset(xoffSet);
xoffSet += REC_OVERHEAD + LONG_SIZE;
int xKeyLen = readInt(buffer, xoffSet);
int xKey = INT_SIZE + xoffSet;
int yoffSet = getOffset(yPosition);
int yIndexID = getIndexIDFromOffset(yoffSet);
yoffSet += REC_OVERHEAD + LONG_SIZE;
int yKeyLen = readInt(buffer, yoffSet);
int yKey = INT_SIZE + yoffSet;
return indexComparator.compare(buffer, xKey, xKeyLen, xIndexID, buffer, yKey, yKeyLen, yIndexID);
}
private int compare(int xPosition, byte[] yKey, int yIndexID)
{
int xoffSet = getOffset(xPosition);
int xIndexID = getIndexIDFromOffset(xoffSet);
xoffSet += REC_OVERHEAD + LONG_SIZE;
int xKeyLen = readInt(buffer, xoffSet);
int xKey = INT_SIZE + xoffSet;
return indexComparator.compare(buffer, xKey, xKeyLen, xIndexID, yKey, 0, yKey.length, yIndexID);
}
/**
* Verifies whether the provided byte array and indexID are equal to
* the byte array and indexIDs currently pointed to by this index output buffer.
*
* @param b The byte array to compare.
* @param bIndexID The index key to compare.
* @return True if the byte arrays are equal.
*/
public boolean sameKeyAndIndexID(byte[] b, int bIndexID)
{
if (b == null)
{
return false;
}
int offset = getOffset(position);
int indexID = getIndexIDFromOffset(offset);
offset += REC_OVERHEAD + LONG_SIZE;
int keyLen = readInt(buffer, offset);
int key = INT_SIZE + offset;
return indexComparator.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 ByteStringBuilder keyBuf = b.getKeyBuf(b.position);
int offset = getOffset(position);
int indexID = getIndexIDFromOffset(offset);
offset += REC_OVERHEAD + LONG_SIZE;
int keyLen = readInt(buffer, offset);
int key = INT_SIZE + offset;
int cmp = indexComparator.compare(buffer, key, keyLen, keyBuf.getBackingArray(), keyBuf.length());
if (cmp == 0)
{
cmp = compareInts(indexID, b.getIndexID());
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;
}
}
/**
* 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 = getOffset(position) + REC_OVERHEAD + LONG_SIZE;
int keyLen = readInt(buffer, offSet);
offSet += INT_SIZE;
dataStream.write(buffer, offSet, keyLen);
}
/**
* 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 compare(position, this.position) == 0;
}
/**
* 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 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)
{
return readInt(buffer, index);
}
private int readInt(byte[] buffer, 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 compare(a,b) < 0
? (compare(b,c) < 0 ? b : compare(a,c) < 0 ? c : a)
: (compare(b,c) > 0 ? b : compare(a,c) > 0 ? c : a);
}
private void sort(int off, int len)
{
if (len < 7) {
for (int i=off; i