/*
|
* 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
|
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
|
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
|
* 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
|
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. 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
|
*
|
*
|
* Portions Copyright 2006-2007 Sun Microsystems, Inc.
|
*/
|
package org.opends.server.backends.jeb;
|
|
import org.opends.server.types.*;
|
import org.opends.server.protocols.asn1.ASN1OctetString;
|
|
import java.util.HashMap;
|
|
import com.sleepycat.je.DatabaseException;
|
|
/**
|
* This class representsa partial sorted set of sorted entries in a VLV
|
* index.
|
*/
|
public class SortValuesSet
|
{
|
private static final int ENCODED_VALUE_SIZE = 16;
|
private static final int ENCODED_VALUE_LENGTH_SIZE =
|
encodedLengthSize(ENCODED_VALUE_SIZE);
|
private static final int ENCODED_ATTRIBUTE_VALUE_SIZE =
|
ENCODED_VALUE_LENGTH_SIZE + ENCODED_VALUE_SIZE;
|
|
private ID2Entry id2entry;
|
|
private long[] entryIDs;
|
|
private byte[] valuesBytes;
|
|
private byte[] keyBytes;
|
|
private HashMap<EntryID, AttributeValue[]> cachedAttributeValues;
|
|
private VLVIndex vlvIndex;
|
|
/**
|
* Construct an empty sort values set with the given information.
|
*
|
* @param vlvIndex The VLV index using this set.
|
* @param id2entry The ID2Entry database.
|
*/
|
public SortValuesSet(VLVIndex vlvIndex, ID2Entry id2entry)
|
{
|
this.keyBytes = new byte[0];
|
this.entryIDs = null;
|
this.valuesBytes = null;
|
this.id2entry = id2entry;
|
this.vlvIndex = vlvIndex;
|
this.cachedAttributeValues = new HashMap<EntryID, AttributeValue[]>();
|
}
|
|
/**
|
* Construct a sort values set from the database.
|
*
|
* @param keyBytes The database key used to locate this set.
|
* @param dataBytes The bytes to decode and construct this set.
|
* @param vlvIndex The VLV index using this set.
|
* @param id2entry The ID2Entry database.
|
*/
|
public SortValuesSet(byte[] keyBytes, byte[] dataBytes, VLVIndex vlvIndex,
|
ID2Entry id2entry)
|
{
|
this.keyBytes = keyBytes;
|
this.id2entry = id2entry;
|
this.vlvIndex = vlvIndex;
|
this.cachedAttributeValues = new HashMap<EntryID, AttributeValue[]>();
|
|
if(dataBytes == null)
|
{
|
entryIDs = new long[0];
|
return;
|
}
|
|
entryIDs = getEncodedIDs(dataBytes, 0);
|
valuesBytes = new byte[entryIDs.length * ENCODED_ATTRIBUTE_VALUE_SIZE *
|
vlvIndex.sortOrder.getSortKeys().length];
|
System.arraycopy(dataBytes, entryIDs.length * 8 + 4, valuesBytes, 0,
|
valuesBytes.length);
|
}
|
|
private SortValuesSet(long[] entryIDs, byte[] keyBytes, byte[] valuesBytes,
|
VLVIndex vlvIndex, ID2Entry id2entry)
|
{
|
this.keyBytes = keyBytes;
|
this.id2entry = id2entry;
|
this.entryIDs = entryIDs;
|
this.valuesBytes = valuesBytes;
|
this.vlvIndex = vlvIndex;
|
this.cachedAttributeValues = new HashMap<EntryID, AttributeValue[]>();
|
}
|
|
/**
|
* Add the given entryID and values from this VLV idnex.
|
*
|
* @param entryID The entry ID to add.
|
* @param values The values to add.
|
* @return True if the information was successfully added or False
|
* otherwise.
|
* @throws DirectoryException If a Directory Server error occurs.
|
* @throws DatabaseException If an error occurs in the JE database.
|
* @throws JebException If an error occurs in the JE database.
|
*/
|
public boolean add(long entryID, AttributeValue[] values)
|
throws JebException, DatabaseException, DirectoryException
|
{
|
if(values == null)
|
{
|
return false;
|
}
|
|
if(entryIDs == null || entryIDs.length == 0)
|
{
|
entryIDs = new long[1];
|
entryIDs[0] = entryID;
|
valuesBytes = attributeValuesToDatabase(values);
|
return true;
|
}
|
if(vlvIndex.comparator.compareValuesInSet(this,
|
entryIDs.length - 1, entryID,
|
values) < 0)
|
{
|
long[] updatedEntryIDs = new long[entryIDs.length + 1];
|
System.arraycopy(entryIDs, 0, updatedEntryIDs, 0, entryIDs.length);
|
updatedEntryIDs[entryIDs.length] = entryID;
|
|
byte[] newValuesBytes = attributeValuesToDatabase(values);
|
byte[] updatedValuesBytes = new byte[valuesBytes.length +
|
newValuesBytes.length];
|
System.arraycopy(valuesBytes, 0, updatedValuesBytes, 0,
|
valuesBytes.length);
|
System.arraycopy(newValuesBytes, 0, updatedValuesBytes,
|
valuesBytes.length,
|
newValuesBytes.length);
|
|
entryIDs = updatedEntryIDs;
|
valuesBytes = updatedValuesBytes;
|
return true;
|
}
|
else
|
{
|
int pos = binarySearch(entryID, values);
|
if(pos >= 0)
|
{
|
if(entryIDs[pos] == entryID)
|
{
|
// The entry ID is alreadly present.
|
return false;
|
}
|
}
|
else
|
{
|
// For a negative return value r, the vlvIndex -(r+1) gives the array
|
// ndex at which the specified value can be inserted to maintain
|
// the sorted order of the array.
|
pos = -(pos+1);
|
}
|
|
long[] updatedEntryIDs = new long[entryIDs.length + 1];
|
System.arraycopy(entryIDs, 0, updatedEntryIDs, 0, pos);
|
System.arraycopy(entryIDs, pos, updatedEntryIDs, pos+1,
|
entryIDs.length-pos);
|
updatedEntryIDs[pos] = entryID;
|
|
byte[] newValuesBytes = attributeValuesToDatabase(values);
|
int valuesPos = pos * newValuesBytes.length;
|
byte[] updatedValuesBytes = new byte[valuesBytes.length +
|
newValuesBytes.length];
|
System.arraycopy(valuesBytes, 0, updatedValuesBytes, 0, valuesPos);
|
System.arraycopy(valuesBytes, valuesPos, updatedValuesBytes,
|
valuesPos + newValuesBytes.length,
|
valuesBytes.length - valuesPos);
|
System.arraycopy(newValuesBytes, 0, updatedValuesBytes, valuesPos,
|
newValuesBytes.length);
|
|
entryIDs = updatedEntryIDs;
|
valuesBytes = updatedValuesBytes;
|
}
|
|
return true;
|
}
|
|
/**
|
* Remove the given entryID and values from this VLV idnex.
|
*
|
* @param entryID The entry ID to remove.
|
* @param values The values to remove.
|
* @return True if the information was successfully removed or False
|
* otherwise.
|
* @throws DirectoryException If a Directory Server error occurs.
|
* @throws DatabaseException If an error occurs in the JE database.
|
* @throws JebException If an error occurs in the JE database.
|
*/
|
public boolean remove(long entryID, AttributeValue[] values)
|
throws JebException, DatabaseException, DirectoryException
|
{
|
if(entryIDs == null || entryIDs.length == 0)
|
{
|
return false;
|
}
|
|
int pos = binarySearch(entryID, values);
|
if(pos < 0)
|
{
|
// Not found.
|
return false;
|
}
|
else
|
{
|
// Found it.
|
long[] updatedEntryIDs = new long[entryIDs.length - 1];
|
System.arraycopy(entryIDs, 0, updatedEntryIDs, 0, pos);
|
System.arraycopy(entryIDs, pos+1, updatedEntryIDs, pos,
|
entryIDs.length-pos-1);
|
|
int valuesLength = ENCODED_ATTRIBUTE_VALUE_SIZE *
|
vlvIndex.sortOrder.getSortKeys().length;
|
int valuesPos = pos * valuesLength;
|
byte[] updatedValuesBytes = new byte[valuesBytes.length - valuesLength];
|
System.arraycopy(valuesBytes, 0, updatedValuesBytes, 0, valuesPos);
|
System.arraycopy(valuesBytes, valuesPos + valuesLength,
|
updatedValuesBytes, valuesPos,
|
valuesBytes.length - valuesPos - valuesLength);
|
|
entryIDs = updatedEntryIDs;
|
valuesBytes = updatedValuesBytes;
|
return true;
|
}
|
}
|
|
/**
|
* Split portions of this set into another set. The values of the new set is
|
* from the front of this set.
|
*
|
* @param splitLength The size of the new set.
|
* @return The split set.
|
*/
|
public SortValuesSet split(int splitLength)
|
{
|
long[] splitEntryIDs = new long[splitLength];
|
byte[] splitValuesBytes = new byte[splitLength *
|
ENCODED_ATTRIBUTE_VALUE_SIZE * vlvIndex.sortOrder.getSortKeys().length];
|
|
long[] updatedEntryIDs = new long[entryIDs.length - splitEntryIDs.length];
|
System.arraycopy(entryIDs, 0, updatedEntryIDs, 0, updatedEntryIDs.length);
|
System.arraycopy(entryIDs, updatedEntryIDs.length, splitEntryIDs, 0,
|
splitEntryIDs.length);
|
|
byte[] updatedValuesBytes = new byte[valuesBytes.length -
|
splitValuesBytes.length];
|
System.arraycopy(valuesBytes, 0, updatedValuesBytes, 0,
|
updatedValuesBytes.length);
|
System.arraycopy(valuesBytes, updatedValuesBytes.length, splitValuesBytes,
|
0, splitValuesBytes.length);
|
|
SortValuesSet splitValuesSet = new SortValuesSet(splitEntryIDs,
|
keyBytes,
|
splitValuesBytes,
|
vlvIndex, id2entry);
|
|
entryIDs = updatedEntryIDs;
|
valuesBytes = updatedValuesBytes;
|
keyBytes = null;
|
|
return splitValuesSet;
|
}
|
|
/**
|
* Encode this set to its database format.
|
*
|
* @return The encoded bytes representing this set.
|
*/
|
public byte[] toDatabase()
|
{
|
byte[] entryIDBytes = JebFormat.entryIDListToDatabase(entryIDs);
|
byte[] concatBytes = new byte[entryIDBytes.length + valuesBytes.length + 4];
|
int v = entryIDs.length;
|
|
for (int j = 3; j >= 0; j--)
|
{
|
concatBytes[j] = (byte) (v & 0xFF);
|
v >>>= 8;
|
}
|
|
System.arraycopy(entryIDBytes, 0, concatBytes, 4, entryIDBytes.length);
|
System.arraycopy(valuesBytes, 0, concatBytes, entryIDBytes.length+4,
|
valuesBytes.length);
|
|
return concatBytes;
|
}
|
|
/**
|
* Get the size of the provided encoded set.
|
*
|
* @param bytes The encoded bytes of a SortValuesSet to decode the size from.
|
* @param offset The byte offset to start decoding.
|
* @return The size of the provided encoded set.
|
*/
|
public static int getEncodedSize(byte[] bytes, int offset)
|
{
|
int v = 0;
|
for (int i = offset; i < offset + 4; i++)
|
{
|
v <<= 8;
|
v |= (bytes[i] & 0xFF);
|
}
|
return v;
|
}
|
|
/**
|
* Get the IDs from the provided encoded set.
|
*
|
* @param bytes The encoded bytes of a SortValuesSet to decode the IDs from.
|
* @param offset The byte offset to start decoding.
|
* @return The decoded IDs in the provided encoded set.
|
*/
|
public static long[] getEncodedIDs(byte[] bytes, int offset)
|
{
|
int length = getEncodedSize(bytes, offset);
|
byte[] entryIDBytes = new byte[length * 8];
|
System.arraycopy(bytes, offset+4, entryIDBytes, 0, entryIDBytes.length);
|
return JebFormat.entryIDListFromDatabase(entryIDBytes);
|
}
|
|
/**
|
* Searches this set for the specified values and entry ID using the binary
|
* search algorithm.
|
*
|
* @param entryID The entry ID to match or -1 if not matching on entry ID.
|
* @param values The values to match.
|
* @return Index of the entry matching the values and optionally the entry ID
|
* if it is found or a negative index if its not found.
|
* @throws DirectoryException If a Directory Server error occurs.
|
* @throws DatabaseException If an error occurs in the JE database.
|
* @throws JebException If an error occurs in the JE database.
|
*/
|
int binarySearch(long entryID, AttributeValue[] values)
|
throws JebException, DatabaseException, DirectoryException
|
{
|
if(entryIDs == null || entryIDs.length == 0)
|
{
|
return -1;
|
}
|
|
int i = 0;
|
for(int j = entryIDs.length - 1; i <= j;)
|
{
|
int k = i + j >> 1;
|
int l = vlvIndex.comparator.compareValuesInSet(this, k, entryID, values);
|
if(l < 0)
|
i = k + 1;
|
else
|
if(l > 0)
|
j = k - 1;
|
else
|
return k;
|
}
|
|
return -(i + 1);
|
}
|
|
/**
|
* Retrieve the size of this set.
|
*
|
* @return The size of this set.
|
*/
|
public int size()
|
{
|
if(entryIDs == null)
|
{
|
return 0;
|
}
|
|
return entryIDs.length;
|
}
|
|
/**
|
* Retrieve the entry IDs in this set.
|
*
|
* @return The entry IDs in this set.
|
*/
|
public long[] getEntryIDs()
|
{
|
return entryIDs;
|
}
|
|
private static int encodedLengthSize(int length)
|
{
|
if ((length & 0x000000FF) == length)
|
{
|
return 1;
|
}
|
else if ((length & 0x0000FFFF) == length)
|
{
|
return 2;
|
}
|
else if ((length & 0x00FFFFFF) == length)
|
{
|
return 3;
|
}
|
else
|
{
|
return 4;
|
}
|
}
|
|
private byte[] attributeValuesToDatabase(AttributeValue[] values)
|
throws DirectoryException
|
{
|
byte[] valuesBytes = new byte[values.length *
|
(ENCODED_ATTRIBUTE_VALUE_SIZE)];
|
for(int i = 0; i < values.length; i++)
|
{
|
AttributeValue value = values[i];
|
int length;
|
byte[] lengthBytes = new byte[ENCODED_VALUE_LENGTH_SIZE];
|
if(value == null)
|
{
|
length = 0;
|
}
|
else
|
{
|
byte[] valueBytes = value.getNormalizedValueBytes();
|
length = valueBytes.length;
|
if(valueBytes.length > ENCODED_VALUE_SIZE)
|
{
|
System.arraycopy(valueBytes, 0, valuesBytes,
|
i * ENCODED_ATTRIBUTE_VALUE_SIZE +
|
ENCODED_VALUE_LENGTH_SIZE,
|
ENCODED_VALUE_SIZE);
|
}
|
else
|
{
|
System.arraycopy(valueBytes, 0, valuesBytes,
|
i * ENCODED_ATTRIBUTE_VALUE_SIZE +
|
ENCODED_VALUE_LENGTH_SIZE,
|
valueBytes.length);
|
}
|
}
|
|
for (int j = ENCODED_VALUE_LENGTH_SIZE - 1; j >= 0; j--)
|
{
|
lengthBytes[j] = (byte) (length & 0xFF);
|
length >>>= 8;
|
}
|
|
System.arraycopy(lengthBytes, 0, valuesBytes,
|
i * (ENCODED_ATTRIBUTE_VALUE_SIZE),
|
lengthBytes.length);
|
}
|
return valuesBytes;
|
}
|
|
/**
|
* Returns the key to use for this set of sort values in the database.
|
*
|
* @return The key as an array of bytes that should be used for this set in
|
* the database or NULL if this set is empty.
|
* @throws DirectoryException If a Directory Server error occurs.
|
* @throws DatabaseException If an error occurs in the JE database.
|
* @throws JebException If an error occurs in the JE database.
|
*/
|
public byte[] getKeyBytes()
|
throws JebException, DatabaseException, DirectoryException
|
{
|
if(entryIDs == null || entryIDs.length == 0)
|
{
|
return null;
|
}
|
|
if(keyBytes != null)
|
{
|
return keyBytes;
|
}
|
|
SortKey[] sortKeys = vlvIndex.sortOrder.getSortKeys();
|
int numValues = sortKeys.length;
|
AttributeValue[] values =
|
new AttributeValue[numValues];
|
for (int i = (entryIDs.length - 1) * numValues, j = 0;
|
i < entryIDs.length * numValues;
|
i++, j++)
|
{
|
values[j] = new AttributeValue(sortKeys[j].getAttributeType(),
|
new ASN1OctetString(getValue(i)));
|
}
|
keyBytes = vlvIndex.encodeKey(entryIDs[entryIDs.length - 1], values);
|
return keyBytes;
|
}
|
|
/**
|
* Returns the key to use for this set of sort values in the database.
|
*
|
* @return The key as a sort values object that should be used for this set in
|
* the database or NULL if this set is empty or unbounded.
|
* @throws DirectoryException If a Directory Server error occurs.
|
* @throws DatabaseException If an error occurs in the JE database.
|
* @throws JebException If an error occurs in the JE database.
|
*/
|
public SortValues getKeySortValues()
|
throws JebException, DatabaseException, DirectoryException
|
{
|
if(entryIDs == null || entryIDs.length == 0)
|
{
|
return null;
|
}
|
|
if(keyBytes != null && keyBytes.length == 0)
|
{
|
return null;
|
}
|
|
EntryID id = new EntryID(entryIDs[entryIDs.length - 1]);
|
SortKey[] sortKeys = vlvIndex.sortOrder.getSortKeys();
|
int numValues = sortKeys.length;
|
AttributeValue[] values =
|
new AttributeValue[numValues];
|
for (int i = (entryIDs.length - 1) * numValues, j = 0;
|
i < entryIDs.length * numValues;
|
i++, j++)
|
{
|
values[j] = new AttributeValue(sortKeys[j].getAttributeType(),
|
new ASN1OctetString(getValue(i)));
|
}
|
|
return new SortValues(id, values, vlvIndex.sortOrder);
|
}
|
|
/**
|
* Returns the sort values at the index in this set.
|
*
|
* @param index The index of the sort values to get.
|
* @return The sort values object at the specified index.
|
* @throws DirectoryException If a Directory Server error occurs.
|
* @throws DatabaseException If an error occurs in the JE database.
|
* @throws JebException If an error occurs in the JE database.
|
**/
|
public SortValues getSortValues(int index)
|
throws JebException, DatabaseException, DirectoryException
|
{
|
if(entryIDs == null || entryIDs.length == 0)
|
{
|
return null;
|
}
|
|
EntryID id = new EntryID(entryIDs[index]);
|
SortKey[] sortKeys = vlvIndex.sortOrder.getSortKeys();
|
int numValues = sortKeys.length;
|
AttributeValue[] values =
|
new AttributeValue[numValues];
|
for (int i = index * numValues, j = 0;
|
i < (index + 1) * numValues;
|
i++, j++)
|
{
|
byte[] value = getValue(i);
|
|
if(value != null)
|
{
|
values[j] = new AttributeValue(sortKeys[j].getAttributeType(),
|
new ASN1OctetString(value));
|
}
|
}
|
|
return new SortValues(id, values, vlvIndex.sortOrder);
|
}
|
|
/**
|
* Retrieve an attribute value from this values set. The index is the
|
* absolute index. (ie. for a sort on 3 attributes per entry, an vlvIndex of 6
|
* will be the 1st attribute value of the 3rd entry).
|
*
|
* @param index The vlvIndex of the attribute value to retrieve.
|
* @return The byte array representation of the attribute value.
|
* @throws DirectoryException If a Directory Server error occurs.
|
* @throws DatabaseException If an error occurs in the JE database.
|
* @throws JebException If an error occurs in the JE database.
|
*/
|
public byte[] getValue(int index)
|
throws JebException, DatabaseException, DirectoryException
|
{
|
// If values bytes is null, we have to get the value by getting the
|
// entry by ID and getting the value.
|
if(valuesBytes != null)
|
{
|
int pos = index * ENCODED_ATTRIBUTE_VALUE_SIZE;
|
int length = 0;
|
byte[] valueBytes;
|
for (int k = 0; k < ENCODED_VALUE_LENGTH_SIZE; k++, pos++)
|
{
|
length <<= 8;
|
length |= (valuesBytes[pos] & 0xFF);
|
}
|
|
if(length == 0)
|
{
|
return null;
|
}
|
// If the value has exceeded the max value size, we have to get the
|
// value by getting the entry by ID.
|
else if(length <= ENCODED_VALUE_SIZE && length > 0)
|
{
|
valueBytes = new byte[length];
|
System.arraycopy(valuesBytes, pos, valueBytes, 0, length);
|
|
return valueBytes;
|
}
|
}
|
|
if(id2entry == null)
|
{
|
return new byte[0];
|
}
|
|
// Get the entry from id2entry and assign the values from the entry.
|
// Once the values are assigned from the retrieved entry, it will
|
// not be retrieve again from future compares.
|
EntryID id = new EntryID(entryIDs[index /
|
vlvIndex.sortOrder.getSortKeys().length]);
|
AttributeValue[] values = cachedAttributeValues.get(id);
|
if(values == null)
|
{
|
values = vlvIndex.getSortValues(id2entry.get(null, id));
|
cachedAttributeValues.put(id, values);
|
}
|
int offset = index % values.length;
|
return values[offset].getNormalizedValueBytes();
|
}
|
|
}
|