1 /*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2002, 2013 Oracle and/or its affiliates. All rights reserved. 5 * 6 */ 7 8 package com.sleepycat.util.keyrange; 9 10 import java.util.Comparator; 11 12 import com.sleepycat.db.DatabaseEntry; 13 14 /** 15 * Encapsulates a key range for use with a RangeCursor. 16 */ 17 public class KeyRange { 18 19 /* 20 * We can return the same byte[] for 0 length arrays. 21 */ 22 public static final byte[] ZERO_LENGTH_BYTE_ARRAY = new byte[0]; 23 24 Comparator<byte[]> comparator; 25 DatabaseEntry beginKey; 26 DatabaseEntry endKey; 27 boolean singleKey; 28 boolean beginInclusive; 29 boolean endInclusive; 30 31 /** 32 * Creates an unconstrained key range. 33 */ KeyRange(Comparator<byte[]> comparator)34 public KeyRange(Comparator<byte[]> comparator) { 35 this.comparator = comparator; 36 } 37 38 /** 39 * Creates a range for a single key. 40 */ subRange(DatabaseEntry key)41 public KeyRange subRange(DatabaseEntry key) 42 throws KeyRangeException { 43 44 if (!check(key)) { 45 throw new KeyRangeException("singleKey out of range"); 46 } 47 KeyRange range = new KeyRange(comparator); 48 range.beginKey = key; 49 range.endKey = key; 50 range.beginInclusive = true; 51 range.endInclusive = true; 52 range.singleKey = true; 53 return range; 54 } 55 56 /** 57 * Creates a range that is the intersection of this range and the given 58 * range parameters. 59 */ subRange(DatabaseEntry beginKey, boolean beginInclusive, DatabaseEntry endKey, boolean endInclusive)60 public KeyRange subRange(DatabaseEntry beginKey, boolean beginInclusive, 61 DatabaseEntry endKey, boolean endInclusive) 62 throws KeyRangeException { 63 64 if (beginKey == null) { 65 beginKey = this.beginKey; 66 beginInclusive = this.beginInclusive; 67 } else if (!check(beginKey, beginInclusive)) { 68 throw new KeyRangeException("beginKey out of range"); 69 } 70 if (endKey == null) { 71 endKey = this.endKey; 72 endInclusive = this.endInclusive; 73 } else if (!check(endKey, endInclusive)) { 74 throw new KeyRangeException("endKey out of range"); 75 } 76 KeyRange range = new KeyRange(comparator); 77 range.beginKey = beginKey; 78 range.endKey = endKey; 79 range.beginInclusive = beginInclusive; 80 range.endInclusive = endInclusive; 81 return range; 82 } 83 84 /** 85 * Returns whether this is a single-key range. 86 */ isSingleKey()87 public final boolean isSingleKey() { 88 return singleKey; 89 } 90 91 /** 92 * Returns the key of a single-key range, or null if not a single-key 93 * range. 94 */ getSingleKey()95 public final DatabaseEntry getSingleKey() { 96 97 return singleKey ? beginKey : null; 98 } 99 100 /** 101 * Returns whether this range has a begin or end bound. 102 */ hasBound()103 public final boolean hasBound() { 104 105 return endKey != null || beginKey != null; 106 } 107 108 /** 109 * Formats this range as a string for debugging. 110 */ 111 @Override toString()112 public String toString() { 113 114 return "[KeyRange " + beginKey + ' ' + beginInclusive + 115 endKey + ' ' + endInclusive + 116 (singleKey ? " single" : ""); 117 } 118 119 /** 120 * Returns whether a given key is within range. 121 */ check(DatabaseEntry key)122 public boolean check(DatabaseEntry key) { 123 124 if (singleKey) { 125 return (compare(key, beginKey) == 0); 126 } else { 127 return checkBegin(key, true) && checkEnd(key, true); 128 } 129 } 130 131 /** 132 * Returns whether a given key is within range. 133 */ check(DatabaseEntry key, boolean inclusive)134 public boolean check(DatabaseEntry key, boolean inclusive) { 135 136 if (singleKey) { 137 return (compare(key, beginKey) == 0); 138 } else { 139 return checkBegin(key, inclusive) && checkEnd(key, inclusive); 140 } 141 } 142 143 /** 144 * Returns whether the given key is within range with respect to the 145 * beginning of the range. 146 * 147 * <p>The inclusive parameter should be true for checking a key read from 148 * the database; this will require that the key is within range. When 149 * inclusive=false the key is allowed to be equal to the beginKey for the 150 * range; this is used for checking a new exclusive bound of a 151 * sub-range.</p> 152 * 153 * <p>Note that when inclusive=false and beginInclusive=true our check is 154 * not exactly correct because in theory we should allow the key to be "one 155 * less" than the existing bound; however, checking for "one less" is 156 * impossible so we do the best we can and test the bounds 157 * conservatively.</p> 158 */ checkBegin(DatabaseEntry key, boolean inclusive)159 public boolean checkBegin(DatabaseEntry key, boolean inclusive) { 160 161 if (beginKey == null) { 162 return true; 163 } else if (!beginInclusive && inclusive) { 164 return compare(key, beginKey) > 0; 165 } else { 166 return compare(key, beginKey) >= 0; 167 } 168 } 169 170 /** 171 * Returns whether the given key is within range with respect to the 172 * end of the range. See checkBegin for details. 173 */ checkEnd(DatabaseEntry key, boolean inclusive)174 public boolean checkEnd(DatabaseEntry key, boolean inclusive) { 175 176 if (endKey == null) { 177 return true; 178 } else if (!endInclusive && inclusive) { 179 return compare(key, endKey) < 0; 180 } else { 181 return compare(key, endKey) <= 0; 182 } 183 } 184 185 /** 186 * Compares two keys, using the user comparator if there is one. 187 */ compare(DatabaseEntry key1, DatabaseEntry key2)188 public int compare(DatabaseEntry key1, DatabaseEntry key2) { 189 190 if (comparator != null) { 191 return comparator.compare(getByteArray(key1), getByteArray(key2)); 192 } else { 193 return compareBytes 194 (key1.getData(), key1.getOffset(), key1.getSize(), 195 key2.getData(), key2.getOffset(), key2.getSize()); 196 197 } 198 } 199 200 /** 201 * Copies a byte array. 202 */ copyBytes(byte[] bytes)203 public static byte[] copyBytes(byte[] bytes) { 204 205 byte[] a = new byte[bytes.length]; 206 System.arraycopy(bytes, 0, a, 0, a.length); 207 return a; 208 } 209 210 /** 211 * Compares two keys as unsigned byte arrays, which is the default 212 * comparison used by JE/DB. 213 */ compareBytes(byte[] data1, int offset1, int size1, byte[] data2, int offset2, int size2)214 public static int compareBytes(byte[] data1, int offset1, int size1, 215 byte[] data2, int offset2, int size2) { 216 217 for (int i = 0; i < size1 && i < size2; i++) { 218 219 int b1 = 0xFF & data1[offset1 + i]; 220 int b2 = 0xFF & data2[offset2 + i]; 221 if (b1 < b2) 222 return -1; 223 else if (b1 > b2) 224 return 1; 225 } 226 227 if (size1 < size2) 228 return -1; 229 else if (size1 > size2) 230 return 1; 231 else 232 return 0; 233 } 234 235 /** 236 * Compares two byte arrays for equality. 237 */ equalBytes(byte[] data1, int offset1, int size1, byte[] data2, int offset2, int size2)238 public static boolean equalBytes(byte[] data1, int offset1, int size1, 239 byte[] data2, int offset2, int size2) { 240 if (size1 != size2) { 241 return false; 242 } 243 for (int i = 0; i < size1; i += 1) { 244 if (data1[i + offset1] != data2[i + offset2]) { 245 return false; 246 } 247 } 248 return true; 249 } 250 251 /** 252 * Returns a copy of an entry. 253 */ copy(DatabaseEntry from)254 public static DatabaseEntry copy(DatabaseEntry from) { 255 return new DatabaseEntry(getByteArray(from)); 256 } 257 258 /** 259 * Copies one entry to another. 260 */ copy(DatabaseEntry from, DatabaseEntry to)261 public static void copy(DatabaseEntry from, DatabaseEntry to) { 262 to.setData(getByteArray(from)); 263 to.setOffset(0); 264 } 265 266 /** 267 * Returns an entry's byte array, copying it if the entry offset is 268 * non-zero. 269 */ getByteArray(DatabaseEntry entry)270 public static byte[] getByteArray(DatabaseEntry entry) { 271 return getByteArrayInternal(entry, Integer.MAX_VALUE); 272 } 273 getByteArray(DatabaseEntry entry, int maxBytes)274 public static byte[] getByteArray(DatabaseEntry entry, int maxBytes) { 275 return getByteArrayInternal(entry, maxBytes); 276 } 277 getByteArrayInternal(DatabaseEntry entry, int maxBytes)278 private static byte[] getByteArrayInternal(DatabaseEntry entry, 279 int maxBytes) { 280 281 byte[] bytes = entry.getData(); 282 if (bytes == null) return null; 283 int size = Math.min(entry.getSize(), maxBytes); 284 byte[] data; 285 if (size == 0) { 286 data = ZERO_LENGTH_BYTE_ARRAY; 287 } else { 288 data = new byte[size]; 289 System.arraycopy(bytes, entry.getOffset(), data, 0, size); 290 } 291 return data; 292 } 293 294 /** 295 * Returns the two DatabaseEntry objects have the same data value. 296 */ equalBytes(DatabaseEntry e1, DatabaseEntry e2)297 public static boolean equalBytes(DatabaseEntry e1, DatabaseEntry e2) { 298 299 if (e1 == null && e2 == null) { 300 return true; 301 } 302 if (e1 == null || e2 == null) { 303 return false; 304 } 305 306 byte[] d1 = e1.getData(); 307 byte[] d2 = e2.getData(); 308 int s1 = e1.getSize(); 309 int s2 = e2.getSize(); 310 int o1 = e1.getOffset(); 311 int o2 = e2.getOffset(); 312 313 if (d1 == null && d2 == null) { 314 return true; 315 } 316 if (d1 == null || d2 == null) { 317 return false; 318 } 319 if (s1 != s2) { 320 return false; 321 } 322 for (int i = 0; i < s1; i += 1) { 323 if (d1[o1 + i] != d2[o2 + i]) { 324 return false; 325 } 326 } 327 return true; 328 } 329 330 /** 331 * Converts the byte array of this thang to space-separated integers, 332 * and suffixed by the record number if applicable. 333 * 334 * @param dbt the thang to convert. 335 * 336 * @return the resulting string. 337 */ toString(DatabaseEntry dbt)338 public static String toString(DatabaseEntry dbt) { 339 340 int len = dbt.getOffset() + dbt.getSize(); 341 StringBuilder buf = new StringBuilder(len * 2); 342 byte[] data = dbt.getData(); 343 for (int i = dbt.getOffset(); i < len; i++) { 344 String num = Integer.toHexString(data[i]); 345 if (num.length() < 2) buf.append('0'); 346 buf.append(num); 347 } 348 return buf.toString(); 349 } 350 } 351