blob: 7c1ef552c712499c1fd2c153dd39fd8cbcd4d13f [file] [log] [blame]
/*-
* Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle Berkeley
* DB Java Edition made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle Berkeley DB Java Edition for a copy of the
* license and additional information.
*/
package com.sleepycat.util.keyrange;
import java.util.Comparator;
import com.sleepycat.je.DatabaseEntry;
/**
* Encapsulates a key range for use with a RangeCursor.
*/
public class KeyRange {
/*
* We can return the same byte[] for 0 length arrays.
*/
public static final byte[] ZERO_LENGTH_BYTE_ARRAY = new byte[0];
Comparator<byte[]> comparator;
DatabaseEntry beginKey;
DatabaseEntry endKey;
boolean singleKey;
boolean beginInclusive;
boolean endInclusive;
/**
* Creates an unconstrained key range.
*/
public KeyRange(Comparator<byte[]> comparator) {
this.comparator = comparator;
}
/**
* Creates a range for a single key.
*/
public KeyRange subRange(DatabaseEntry key)
throws KeyRangeException {
if (!check(key)) {
throw new KeyRangeException("singleKey out of range");
}
KeyRange range = new KeyRange(comparator);
range.beginKey = key;
range.endKey = key;
range.beginInclusive = true;
range.endInclusive = true;
range.singleKey = true;
return range;
}
/**
* Creates a range that is the intersection of this range and the given
* range parameters.
*/
public KeyRange subRange(DatabaseEntry beginKey, boolean beginInclusive,
DatabaseEntry endKey, boolean endInclusive)
throws KeyRangeException {
if (beginKey == null) {
beginKey = this.beginKey;
beginInclusive = this.beginInclusive;
} else if (!check(beginKey, beginInclusive)) {
throw new KeyRangeException("beginKey out of range");
}
if (endKey == null) {
endKey = this.endKey;
endInclusive = this.endInclusive;
} else if (!check(endKey, endInclusive)) {
throw new KeyRangeException("endKey out of range");
}
KeyRange range = new KeyRange(comparator);
range.beginKey = beginKey;
range.endKey = endKey;
range.beginInclusive = beginInclusive;
range.endInclusive = endInclusive;
return range;
}
/**
* Returns whether this is a single-key range.
*/
public final boolean isSingleKey() {
return singleKey;
}
/**
* Returns the key of a single-key range, or null if not a single-key
* range.
*/
public final DatabaseEntry getSingleKey() {
return singleKey ? beginKey : null;
}
/**
* Returns whether this range has a begin or end bound.
*/
public final boolean hasBound() {
return endKey != null || beginKey != null;
}
/**
* Formats this range as a string for debugging.
*/
@Override
public String toString() {
return "[KeyRange " + beginKey + ' ' + beginInclusive +
endKey + ' ' + endInclusive +
(singleKey ? " single" : "");
}
/**
* Returns whether a given key is within range.
*/
public boolean check(DatabaseEntry key) {
if (singleKey) {
return (compare(key, beginKey) == 0);
} else {
return checkBegin(key, true) && checkEnd(key, true);
}
}
/**
* Returns whether a given key is within range.
*/
public boolean check(DatabaseEntry key, boolean inclusive) {
if (singleKey) {
return (compare(key, beginKey) == 0);
} else {
return checkBegin(key, inclusive) && checkEnd(key, inclusive);
}
}
/**
* Returns whether the given key is within range with respect to the
* beginning of the range.
*
* <p>The inclusive parameter should be true for checking a key read from
* the database; this will require that the key is within range. When
* inclusive=false the key is allowed to be equal to the beginKey for the
* range; this is used for checking a new exclusive bound of a
* sub-range.</p>
*
* <p>Note that when inclusive=false and beginInclusive=true our check is
* not exactly correct because in theory we should allow the key to be "one
* less" than the existing bound; however, checking for "one less" is
* impossible so we do the best we can and test the bounds
* conservatively.</p>
*/
public boolean checkBegin(DatabaseEntry key, boolean inclusive) {
if (beginKey == null) {
return true;
} else if (!beginInclusive && inclusive) {
return compare(key, beginKey) > 0;
} else {
return compare(key, beginKey) >= 0;
}
}
/**
* Returns whether the given key is within range with respect to the
* end of the range. See checkBegin for details.
*/
public boolean checkEnd(DatabaseEntry key, boolean inclusive) {
if (endKey == null) {
return true;
} else if (!endInclusive && inclusive) {
return compare(key, endKey) < 0;
} else {
return compare(key, endKey) <= 0;
}
}
/**
* Compares two keys, using the user comparator if there is one.
*/
public int compare(DatabaseEntry key1, DatabaseEntry key2) {
if (comparator != null) {
return comparator.compare(getByteArray(key1), getByteArray(key2));
} else {
return compareBytes
(key1.getData(), key1.getOffset(), key1.getSize(),
key2.getData(), key2.getOffset(), key2.getSize());
}
}
/**
* Copies a byte array.
*/
public static byte[] copyBytes(byte[] bytes) {
byte[] a = new byte[bytes.length];
System.arraycopy(bytes, 0, a, 0, a.length);
return a;
}
/**
* Compares two keys as unsigned byte arrays, which is the default
* comparison used by JE/DB.
*/
public static int compareBytes(byte[] data1, int offset1, int size1,
byte[] data2, int offset2, int size2) {
for (int i = 0; i < size1 && i < size2; i++) {
int b1 = 0xFF & data1[offset1 + i];
int b2 = 0xFF & data2[offset2 + i];
if (b1 < b2)
return -1;
else if (b1 > b2)
return 1;
}
if (size1 < size2)
return -1;
else if (size1 > size2)
return 1;
else
return 0;
}
/**
* Compares two byte arrays for equality.
*/
public static boolean equalBytes(byte[] data1, int offset1, int size1,
byte[] data2, int offset2, int size2) {
if (size1 != size2) {
return false;
}
for (int i = 0; i < size1; i += 1) {
if (data1[i + offset1] != data2[i + offset2]) {
return false;
}
}
return true;
}
/**
* Returns a copy of an entry.
*/
public static DatabaseEntry copy(DatabaseEntry from) {
return new DatabaseEntry(getByteArray(from));
}
/**
* Copies one entry to another.
*/
public static void copy(DatabaseEntry from, DatabaseEntry to) {
to.setData(getByteArray(from));
to.setOffset(0);
}
/**
* Returns an entry's byte array, copying it if the entry offset is
* non-zero.
*/
public static byte[] getByteArray(DatabaseEntry entry) {
return getByteArrayInternal(entry, Integer.MAX_VALUE);
}
public static byte[] getByteArray(DatabaseEntry entry, int maxBytes) {
return getByteArrayInternal(entry, maxBytes);
}
private static byte[] getByteArrayInternal(DatabaseEntry entry,
int maxBytes) {
byte[] bytes = entry.getData();
if (bytes == null) return null;
int size = Math.min(entry.getSize(), maxBytes);
byte[] data;
if (size == 0) {
data = ZERO_LENGTH_BYTE_ARRAY;
} else {
data = new byte[size];
System.arraycopy(bytes, entry.getOffset(), data, 0, size);
}
return data;
}
/**
* Returns the two DatabaseEntry objects have the same data value.
*/
public static boolean equalBytes(DatabaseEntry e1, DatabaseEntry e2) {
if (e1 == null && e2 == null) {
return true;
}
if (e1 == null || e2 == null) {
return false;
}
byte[] d1 = e1.getData();
byte[] d2 = e2.getData();
int s1 = e1.getSize();
int s2 = e2.getSize();
int o1 = e1.getOffset();
int o2 = e2.getOffset();
if (d1 == null && d2 == null) {
return true;
}
if (d1 == null || d2 == null) {
return false;
}
if (s1 != s2) {
return false;
}
for (int i = 0; i < s1; i += 1) {
if (d1[o1 + i] != d2[o2 + i]) {
return false;
}
}
return true;
}
/**
* Converts the byte array of this thang to space-separated integers,
* and suffixed by the record number if applicable.
*
* @param dbt the thang to convert.
*
* @return the resulting string.
*/
public static String toString(DatabaseEntry dbt) {
int len = dbt.getOffset() + dbt.getSize();
StringBuilder buf = new StringBuilder(len * 2);
byte[] data = dbt.getData();
for (int i = dbt.getOffset(); i < len; i++) {
String num = Integer.toHexString(data[i]);
if (num.length() < 2) buf.append('0');
buf.append(num);
}
return buf.toString();
}
}