1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19 package org.apache.hadoop.hbase.util;
20
21
22
23
24 /**
25 * Lightweight, reusable class for specifying ranges of byte[]'s. CompareTo and equals methods are
26 * lexicographic, which is native to HBase.
27 * <p/>
28 * This class differs from ByteBuffer:
29 * <li/>On-heap bytes only
30 * <li/>Implements equals, hashCode, and compareTo so that it can be used in standard java
31 * Collections, similar to String.
32 * <li/>Does not maintain mark/position iterator state inside the class. Doing so leads to many bugs
33 * in complex applications.
34 * <li/>Allows the addition of simple core methods like this.copyTo(that, offset).
35 * <li/>Can be reused in tight loops like a major compaction which can save significant amounts of
36 * garbage.
37 * <li/>(Without reuse, we throw off garbage like this thing:
38 * http://www.youtube.com/watch?v=lkmBH-MjZF4
39 * <p/>
40 * Mutable, and always evaluates equals, hashCode, and compareTo based on the current contents.
41 * <p/>
42 * Can contain convenience methods for comparing, printing, cloning, spawning new arrays, copying to
43 * other arrays, etc. Please place non-core methods into {@link ByteRangeTool}.
44 * <p/>
45 * We may consider converting this to an interface and creating separate implementations for a
46 * single byte[], a paged byte[] (growable byte[][]), a ByteBuffer, etc
47 */
48 public class ByteRange implements Comparable<ByteRange> {
49
50 private static final int UNSET_HASH_VALUE = -1;
51
52
53 /********************** fields *****************************/
54
55 // Do not make these final, as the intention is to reuse objects of this class
56
57 /**
58 * The array containing the bytes in this range. It will be >= length.
59 */
60 private byte[] bytes;
61
62 /**
63 * The index of the first byte in this range. ByteRange.get(0) will return bytes[offset].
64 */
65 private int offset;
66
67 /**
68 * The number of bytes in the range. Offset + length must be <= bytes.length
69 */
70 private int length;
71
72 /**
73 * Variable for lazy-caching the hashCode of this range. Useful for frequently used ranges,
74 * long-lived ranges, or long ranges.
75 */
76 private int hash = UNSET_HASH_VALUE;
77
78
79 /********************** construct ***********************/
80
81 public ByteRange() {
82 set(new byte[0]);//Could probably get away with a null array if the need arises.
83 }
84
85 public ByteRange(byte[] bytes) {
86 set(bytes);
87 }
88
89 public ByteRange(byte[] bytes, int offset, int length) {
90 set(bytes, offset, length);
91 }
92
93
94 /********************** write methods *************************/
95
96 public ByteRange clear() {
97 clearHashCache();
98 bytes = null;
99 offset = 0;
100 length = 0;
101 return this;
102 }
103
104 public ByteRange set(byte[] bytes) {
105 clearHashCache();
106 this.bytes = bytes;
107 this.offset = 0;
108 this.length = ArrayUtils.length(bytes);
109 return this;
110 }
111
112 public ByteRange set(byte[] bytes, int offset, int length) {
113 clearHashCache();
114 this.bytes = bytes;
115 this.offset = offset;
116 this.length = length;
117 return this;
118 }
119
120 public void setLength(int length) {
121 clearHashCache();
122 this.length = length;
123 }
124
125
126 /*********** read methods (add non-core methods to ByteRangeUtils) *************/
127
128 /**
129 * @param index zero-based index
130 * @return single byte at index
131 */
132 public byte get(int index) {
133 return bytes[offset + index];
134 }
135
136 /**
137 * Instantiate a new byte[] with exact length, which is at least 24 bytes + length. Copy the
138 * contents of this range into it.
139 * @return The newly cloned byte[].
140 */
141 public byte[] deepCopyToNewArray() {
142 byte[] result = new byte[length];
143 System.arraycopy(bytes, offset, result, 0, length);
144 return result;
145 }
146
147 /**
148 * Create a new ByteRange with new backing byte[] and copy the state of this range into the new
149 * range. Copy the hash over if it is already calculated.
150 * @return Deep copy
151 */
152 public ByteRange deepCopy() {
153 ByteRange clone = new ByteRange(deepCopyToNewArray());
154 if (isHashCached()) {
155 clone.hash = hash;
156 }
157 return clone;
158 }
159
160 /**
161 * Wrapper for System.arraycopy. Copy the contents of this range into the provided array.
162 * @param destination Copy to this array
163 * @param destinationOffset First index in the destination array.
164 */
165 public void deepCopyTo(byte[] destination, int destinationOffset) {
166 System.arraycopy(bytes, offset, destination, destinationOffset, length);
167 }
168
169 /**
170 * Wrapper for System.arraycopy. Copy the contents of this range into the provided array.
171 * @param innerOffset Start copying from this index in this source ByteRange. First byte copied is
172 * bytes[offset + innerOffset]
173 * @param copyLength Copy this many bytes
174 * @param destination Copy to this array
175 * @param destinationOffset First index in the destination array.
176 */
177 public void deepCopySubRangeTo(int innerOffset, int copyLength, byte[] destination,
178 int destinationOffset) {
179 System.arraycopy(bytes, offset + innerOffset, destination, destinationOffset, copyLength);
180 }
181
182 /**
183 * Create a new ByteRange that points at this range's byte[]. The new range can have different
184 * values for offset and length, but modifying the shallowCopy will modify the bytes in this
185 * range's array. Pass over the hash code if it is already cached.
186 * @param innerOffset First byte of clone will be this.offset + copyOffset.
187 * @param copyLength Number of bytes in the clone.
188 * @return new ByteRange object referencing this range's byte[].
189 */
190 public ByteRange shallowCopySubRange(int innerOffset, int copyLength) {
191 ByteRange clone = new ByteRange(bytes, offset + innerOffset, copyLength);
192 if (isHashCached()) {
193 clone.hash = hash;
194 }
195 return clone;
196 }
197
198 //TODO move to ByteRangeUtils because it is non-core method
199 public int numEqualPrefixBytes(ByteRange that, int thatInnerOffset) {
200 int maxCompares = Math.min(length, that.length - thatInnerOffset);
201 for (int i = 0; i < maxCompares; ++i) {
202 if (bytes[offset + i] != that.bytes[that.offset + thatInnerOffset + i]) {
203 return i;
204 }
205 }
206 return maxCompares;
207 }
208
209 public byte[] getBytes() {
210 return bytes;
211 }
212
213 public int getOffset() {
214 return offset;
215 }
216
217 public int getLength() {
218 return length;
219 }
220
221 public boolean isEmpty(){
222 return isEmpty(this);
223 }
224
225 public boolean notEmpty(){
226 return notEmpty(this);
227 }
228
229
230 /******************* static methods ************************/
231
232 public static boolean isEmpty(ByteRange range){
233 return range == null || range.length == 0;
234 }
235
236 public static boolean notEmpty(ByteRange range){
237 return range != null && range.length > 0;
238 }
239
240 /******************* standard methods *********************/
241
242 @Override
243 public boolean equals(Object thatObject) {
244 if (thatObject == null){
245 return false;
246 }
247 if (this == thatObject) {
248 return true;
249 }
250 if (hashCode() != thatObject.hashCode()) {
251 return false;
252 }
253 if (!(thatObject instanceof ByteRange)) {
254 return false;
255 }
256 ByteRange that = (ByteRange) thatObject;
257 return Bytes.equals(bytes, offset, length, that.bytes, that.offset, that.length);
258 }
259
260 @Override
261 public int hashCode() {
262 if (isHashCached()) {// hash is already calculated and cached
263 return hash;
264 }
265 if (this.isEmpty()) {// return 0 for empty ByteRange
266 hash = 0;
267 return hash;
268 }
269 int off = offset;
270 hash = 0;
271 for (int i = 0; i < length; i++) {
272 hash = 31 * hash + bytes[off++];
273 }
274 return hash;
275 }
276
277 private boolean isHashCached() {
278 return hash != UNSET_HASH_VALUE;
279 }
280
281 private void clearHashCache() {
282 hash = UNSET_HASH_VALUE;
283 }
284
285 /**
286 * Bitwise comparison of each byte in the array. Unsigned comparison, not paying attention to
287 * java's signed bytes.
288 */
289 @Override
290 public int compareTo(ByteRange other) {
291 return Bytes.compareTo(bytes, offset, length, other.bytes, other.offset, other.length);
292 }
293
294 @Override
295 public String toString() {
296 return Bytes.toStringBinary(bytes, offset, length);
297 }
298
299 }