/* * 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 * * * Copyright 2006-2008 Sun Microsystems, Inc. */ package org.opends.server.backends.jeb; import org.opends.server.types.*; import com.sleepycat.je.DatabaseException; /** * This class represents a partial sorted set of sorted entries in a VLV * index. */ public class SortValuesSet { private long[] entryIDs; private int[] valuesBytesOffsets; private byte[] valuesBytes; private byte[] keyBytes; private VLVIndex vlvIndex; /** * Construct an empty sort values set with the given information. * * @param vlvIndex The VLV index using this set. */ public SortValuesSet(VLVIndex vlvIndex) { this.keyBytes = new byte[0]; this.entryIDs = null; this.valuesBytes = null; this.valuesBytesOffsets = null; this.vlvIndex = vlvIndex; } /** * 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. */ public SortValuesSet(byte[] keyBytes, byte[] dataBytes, VLVIndex vlvIndex) { this.keyBytes = keyBytes; this.vlvIndex = vlvIndex; if(dataBytes == null) { entryIDs = new long[0]; return; } entryIDs = getEncodedIDs(dataBytes, 0); int valuesBytesOffset = entryIDs.length * 8 + 4; int valuesBytesLength = dataBytes.length - valuesBytesOffset; valuesBytes = new byte[valuesBytesLength]; System.arraycopy(dataBytes, valuesBytesOffset, valuesBytes, 0, valuesBytesLength); this.valuesBytesOffsets = null; } private SortValuesSet() {} /** * 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. */ public boolean add(long entryID, AttributeValue[] values) throws DatabaseException, DirectoryException { if(values == null) { return false; } if(entryIDs == null || entryIDs.length == 0) { entryIDs = new long[1]; entryIDs[0] = entryID; valuesBytes = attributeValuesToDatabase(values); if(valuesBytesOffsets != null) { valuesBytesOffsets = new int[1]; valuesBytesOffsets[0] = 0; } return true; } if(vlvIndex.comparator.compare(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); if(valuesBytesOffsets != null) { int[] updatedValuesBytesOffsets = new int[valuesBytesOffsets.length + 1]; System.arraycopy(valuesBytesOffsets, 0, updatedValuesBytesOffsets, 0, valuesBytesOffsets.length); updatedValuesBytesOffsets[valuesBytesOffsets.length] = updatedValuesBytes.length - newValuesBytes.length; valuesBytesOffsets = updatedValuesBytesOffsets; } 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); // BUG valuesBytesOffsets might be null ? If not why testing below ? int valuesPos = valuesBytesOffsets[pos]; 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); if(valuesBytesOffsets != null) { int[] updatedValuesBytesOffsets = new int[valuesBytesOffsets.length + 1]; System.arraycopy(valuesBytesOffsets, 0, updatedValuesBytesOffsets, 0, pos); // Update the rest of the offsets one by one - Expensive! for(int i = pos; i < valuesBytesOffsets.length; i++) { updatedValuesBytesOffsets[i+1] = valuesBytesOffsets[i] + newValuesBytes.length; } updatedValuesBytesOffsets[pos] = valuesBytesOffsets[pos]; valuesBytesOffsets = updatedValuesBytesOffsets; } 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. */ public boolean remove(long entryID, AttributeValue[] values) throws DatabaseException, DirectoryException { if(entryIDs == null || entryIDs.length == 0) { return false; } if(valuesBytesOffsets == null) { updateValuesBytesOffsets(); } 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; int valuesPos = valuesBytesOffsets[pos]; if(pos < valuesBytesOffsets.length - 1) { valuesLength = valuesBytesOffsets[pos+1] - valuesPos; } else { valuesLength = valuesBytes.length - valuesPos; } 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); int[] updatedValuesBytesOffsets = new int[valuesBytesOffsets.length - 1]; System.arraycopy(valuesBytesOffsets, 0, updatedValuesBytesOffsets, 0, pos); // Update the rest of the offsets one by one - Expensive! for(int i = pos + 1; i < valuesBytesOffsets.length; i++) { updatedValuesBytesOffsets[i-1] = valuesBytesOffsets[i] - valuesLength; } entryIDs = updatedEntryIDs; valuesBytes = updatedValuesBytes; valuesBytesOffsets = updatedValuesBytesOffsets; return true; } } /** * Split portions of this set into another set. The values of the new set is * from the end of this set. * * @param splitLength The size of the new set. * @return The split set. */ public SortValuesSet split(int splitLength) { if(valuesBytesOffsets == null) { updateValuesBytesOffsets(); } long[] splitEntryIDs = new long[splitLength]; byte[] splitValuesBytes = new byte[valuesBytes.length - valuesBytesOffsets[valuesBytesOffsets.length - splitLength]]; int[] splitValuesBytesOffsets = new int[splitLength]; 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[valuesBytesOffsets[valuesBytesOffsets.length - splitLength]]; System.arraycopy(valuesBytes, 0, updatedValuesBytes, 0, updatedValuesBytes.length); System.arraycopy(valuesBytes, updatedValuesBytes.length, splitValuesBytes, 0, splitValuesBytes.length); int[] updatedValuesBytesOffsets = new int[valuesBytesOffsets.length - splitValuesBytesOffsets.length]; System.arraycopy(valuesBytesOffsets, 0, updatedValuesBytesOffsets, 0, updatedValuesBytesOffsets.length); for(int i = updatedValuesBytesOffsets.length; i < valuesBytesOffsets.length; i++) { splitValuesBytesOffsets[i - updatedValuesBytesOffsets.length] = valuesBytesOffsets[i] - valuesBytesOffsets[updatedValuesBytesOffsets.length]; } SortValuesSet splitValuesSet = new SortValuesSet(); splitValuesSet.entryIDs = splitEntryIDs; splitValuesSet.keyBytes = this.keyBytes; splitValuesSet.valuesBytes = splitValuesBytes; splitValuesSet.valuesBytesOffsets = splitValuesBytesOffsets; splitValuesSet.vlvIndex = this.vlvIndex; entryIDs = updatedEntryIDs; valuesBytes = updatedValuesBytes; valuesBytesOffsets = updatedValuesBytesOffsets; keyBytes = null; return splitValuesSet; } /** * Encode this set to its database format. * * @return The encoded bytes representing this set or null if * this set is empty. */ public byte[] toDatabase() { if(size() == 0) { return null; } 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. */ int binarySearch(long entryID, AttributeValue[] values) throws 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.compare(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 byte[] attributeValuesToDatabase(AttributeValue[] values) throws DirectoryException { ByteStringBuilder builder = new ByteStringBuilder(); for (AttributeValue v : values) { if(v == null) { builder.appendBERLength(0); } else { builder.appendBERLength(v.getNormalizedValue().length()); builder.append(v.getNormalizedValue()); } } builder.trimToSize(); return builder.getBackingArray(); } /** * 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. */ public byte[] getKeyBytes() throws DatabaseException, DirectoryException { if(entryIDs == null || entryIDs.length == 0) { return null; } if(keyBytes != null) { return keyBytes; } if(valuesBytesOffsets == null) { updateValuesBytesOffsets(); } int vBytesPos = valuesBytesOffsets[valuesBytesOffsets.length - 1]; int vBytesLength = valuesBytes.length - vBytesPos; byte[] idBytes = JebFormat.entryIDToDatabase(entryIDs[entryIDs.length - 1]); keyBytes = new byte[vBytesLength + idBytes.length]; System.arraycopy(valuesBytes, vBytesPos, keyBytes, 0, vBytesLength); System.arraycopy(idBytes, 0, keyBytes, vBytesLength, idBytes.length); 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. */ public SortValues getKeySortValues() throws 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] = AttributeValues.create( sortKeys[j].getAttributeType(), 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++) { ByteString value = getValue(i); if(value != null) { values[j] = AttributeValues.create( sortKeys[j].getAttributeType(), value); } } return new SortValues(id, values, vlvIndex.sortOrder); } private void updateValuesBytesOffsets() { valuesBytesOffsets = new int[entryIDs.length]; int vBytesPos = 0; int numAttributes = vlvIndex.sortOrder.getSortKeys().length; for(int pos = 0; pos < entryIDs.length; pos++) { valuesBytesOffsets[pos] = vBytesPos; for(int i = 0; i < numAttributes; i++) { int valueLength = valuesBytes[vBytesPos] & 0x7F; if (valueLength != valuesBytes[vBytesPos++]) { int valueLengthBytes = valueLength; valueLength = 0; for (int j=0; j < valueLengthBytes; j++, vBytesPos++) { valueLength = (valueLength << 8) | (valuesBytes[vBytesPos] & 0xFF); } } vBytesPos += valueLength; } } } /** * 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. */ public ByteString getValue(int index) throws DatabaseException, DirectoryException { if(valuesBytesOffsets == null) { updateValuesBytesOffsets(); } int numAttributes = vlvIndex.sortOrder.getSortKeys().length; int vIndex = index / numAttributes; int vOffset = index % numAttributes; int vBytesPos = valuesBytesOffsets[vIndex]; // Find the desired value in the sort order set. for(int i = 0; i <= vOffset; i++) { int valueLength = valuesBytes[vBytesPos] & 0x7F; if (valueLength != valuesBytes[vBytesPos++]) { int valueLengthBytes = valueLength; valueLength = 0; for (int j=0; j < valueLengthBytes; j++, vBytesPos++) { valueLength = (valueLength << 8) | (valuesBytes[vBytesPos] & 0xFF); } } if(i == vOffset) { if(valueLength == 0) { return null; } else { byte[] valueBytes = new byte[valueLength]; System.arraycopy(valuesBytes, vBytesPos, valueBytes, 0, valueLength); return ByteString.wrap(valueBytes); } } else { vBytesPos += valueLength; } } return ByteString.wrap(new byte[0]); } }