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