001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.client; 019 020import edu.umd.cs.findbugs.annotations.CheckForNull; 021import java.io.DataInputStream; 022import java.io.IOException; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Comparator; 026import java.util.List; 027import java.util.stream.Collectors; 028import org.apache.hadoop.hbase.HConstants; 029import org.apache.hadoop.hbase.TableName; 030import org.apache.hadoop.hbase.exceptions.DeserializationException; 031import org.apache.hadoop.hbase.util.ByteArrayHashKey; 032import org.apache.hadoop.hbase.util.Bytes; 033import org.apache.hadoop.hbase.util.HashKey; 034import org.apache.hadoop.hbase.util.JenkinsHash; 035import org.apache.hadoop.hbase.util.MD5Hash; 036import org.apache.hadoop.io.DataInputBuffer; 037import org.apache.hadoop.io.IOUtils; 038import org.apache.yetus.audience.InterfaceAudience; 039 040import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 041import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; 042 043/** 044 * Information about a region. A region is a range of keys in the whole keyspace of a table, an 045 * identifier (a timestamp) for differentiating between subset ranges (after region split) and a 046 * replicaId for differentiating the instance for the same range and some status information about 047 * the region. The region has a unique name which consists of the following fields: 048 * <ul> 049 * <li>tableName : The name of the table</li> 050 * <li>startKey : The startKey for the region.</li> 051 * <li>regionId : A timestamp when the region is created.</li> 052 * <li>replicaId : An id starting from 0 to differentiate replicas of the same region range but 053 * hosted in separated servers. The same region range can be hosted in multiple locations.</li> 054 * <li>encodedName : An MD5 encoded string for the region name.</li> 055 * </ul> 056 * <br> 057 * Other than the fields in the region name, region info contains: 058 * <ul> 059 * <li>endKey : the endKey for the region (exclusive)</li> 060 * <li>split : Whether the region is split</li> 061 * <li>offline : Whether the region is offline</li> 062 * </ul> 063 */ 064@InterfaceAudience.Public 065public interface RegionInfo extends Comparable<RegionInfo> { 066 067 /** 068 * Separator used to demarcate the encodedName in a region name in the new format. See description 069 * on new format above. 070 */ 071 @InterfaceAudience.Private 072 int ENC_SEPARATOR = '.'; 073 074 @InterfaceAudience.Private 075 int MD5_HEX_LENGTH = 32; 076 077 @InterfaceAudience.Private 078 int DEFAULT_REPLICA_ID = 0; 079 080 /** 081 * to keep appended int's sorted in string format. Only allows 2 bytes to be sorted for replicaId. 082 */ 083 @InterfaceAudience.Private 084 String REPLICA_ID_FORMAT = "%04X"; 085 086 @InterfaceAudience.Private 087 byte REPLICA_ID_DELIMITER = (byte) '_'; 088 089 @InterfaceAudience.Private 090 String INVALID_REGION_NAME_FORMAT_MESSAGE = "Invalid regionName format"; 091 092 @InterfaceAudience.Private 093 Comparator<RegionInfo> COMPARATOR = (RegionInfo lhs, RegionInfo rhs) -> { 094 if (rhs == null) { 095 return 1; 096 } 097 098 // Are regions of same table? 099 int result = lhs.getTable().compareTo(rhs.getTable()); 100 if (result != 0) { 101 return result; 102 } 103 104 // Compare start keys. 105 result = Bytes.compareTo(lhs.getStartKey(), rhs.getStartKey()); 106 if (result != 0) { 107 return result; 108 } 109 110 // Compare end keys. 111 result = Bytes.compareTo(lhs.getEndKey(), rhs.getEndKey()); 112 113 if (result != 0) { 114 if (lhs.getStartKey().length != 0 && lhs.getEndKey().length == 0) { 115 return 1; // this is last region 116 } 117 if (rhs.getStartKey().length != 0 && rhs.getEndKey().length == 0) { 118 return -1; // o is the last region 119 } 120 return result; 121 } 122 123 // regionId is usually milli timestamp -- this defines older stamps 124 // to be "smaller" than newer stamps in sort order. 125 if (lhs.getRegionId() > rhs.getRegionId()) { 126 return 1; 127 } else if (lhs.getRegionId() < rhs.getRegionId()) { 128 return -1; 129 } 130 131 int replicaDiff = lhs.getReplicaId() - rhs.getReplicaId(); 132 if (replicaDiff != 0) { 133 return replicaDiff; 134 } 135 136 if (lhs.isOffline() == rhs.isOffline()) { 137 return 0; 138 } 139 if (lhs.isOffline()) { 140 return -1; 141 } 142 143 return 1; 144 }; 145 146 /** 147 * Returns Return a short, printable name for this region (usually encoded name) for us logging. 148 */ 149 String getShortNameToLog(); 150 151 /** Returns the regionId. */ 152 long getRegionId(); 153 154 /** 155 * @return the regionName as an array of bytes. 156 * @see #getRegionNameAsString() 157 */ 158 byte[] getRegionName(); 159 160 /** Returns Region name as a String for use in logging, etc. */ 161 String getRegionNameAsString(); 162 163 /** Returns the encoded region name. */ 164 String getEncodedName(); 165 166 /** Returns the encoded region name as an array of bytes. */ 167 byte[] getEncodedNameAsBytes(); 168 169 /** Returns the startKey. */ 170 byte[] getStartKey(); 171 172 /** Returns the endKey. */ 173 byte[] getEndKey(); 174 175 /** Returns current table name of the region */ 176 TableName getTable(); 177 178 /** Returns returns region replica id */ 179 int getReplicaId(); 180 181 /** Returns True if has been split and has daughters. */ 182 boolean isSplit(); 183 184 /** 185 * @return True if this region is offline. 186 * @deprecated since 3.0.0 and will be removed in 4.0.0 187 * @see <a href="https://issues.apache.org/jira/browse/HBASE-25210">HBASE-25210</a> 188 */ 189 @Deprecated 190 boolean isOffline(); 191 192 /** 193 * @return True if this is a split parent region. 194 * @deprecated since 3.0.0 and will be removed in 4.0.0, Use {@link #isSplit()} instead. 195 * @see <a href="https://issues.apache.org/jira/browse/HBASE-25210">HBASE-25210</a> 196 */ 197 @Deprecated 198 boolean isSplitParent(); 199 200 /** Returns true if this region is a meta region. */ 201 boolean isMetaRegion(); 202 203 /** 204 * @return true if the given inclusive range of rows is fully contained by this region. For 205 * example, if the region is foo,a,g and this is passed ["b","c"] or ["a","c"] it will 206 * return true, but if this is passed ["b","z"] it will return false. 207 * @throws IllegalArgumentException if the range passed is invalid (ie. end < start) 208 */ 209 boolean containsRange(byte[] rangeStartKey, byte[] rangeEndKey); 210 211 /** Returns true if the given row falls in this region. */ 212 boolean containsRow(byte[] row); 213 214 /** 215 * Does region name contain its encoded name? 216 * @param regionName region name 217 * @return boolean indicating if this a new format region name which contains its encoded name. 218 */ 219 @InterfaceAudience.Private 220 static boolean hasEncodedName(final byte[] regionName) { 221 // check if region name ends in ENC_SEPARATOR 222 return (regionName.length >= 1) 223 && (regionName[regionName.length - 1] == RegionInfo.ENC_SEPARATOR); 224 } 225 226 /** Returns the encodedName */ 227 @InterfaceAudience.Private 228 static String encodeRegionName(final byte[] regionName) { 229 String encodedName; 230 if (hasEncodedName(regionName)) { 231 // region is in new format: 232 // <tableName>,<startKey>,<regionIdTimeStamp>/encodedName/ 233 encodedName = 234 Bytes.toString(regionName, regionName.length - MD5_HEX_LENGTH - 1, MD5_HEX_LENGTH); 235 } else { 236 // old format region name. First hbase:meta region also 237 // use this format.EncodedName is the JenkinsHash value. 238 HashKey<byte[]> key = new ByteArrayHashKey(regionName, 0, regionName.length); 239 int hashVal = Math.abs(JenkinsHash.getInstance().hash(key, 0)); 240 encodedName = String.valueOf(hashVal); 241 } 242 return encodedName; 243 } 244 245 @InterfaceAudience.Private 246 static String getRegionNameAsString(byte[] regionName) { 247 return getRegionNameAsString(null, regionName); 248 } 249 250 @InterfaceAudience.Private 251 static String getRegionNameAsString(@CheckForNull RegionInfo ri, byte[] regionName) { 252 if (RegionInfo.hasEncodedName(regionName)) { 253 // new format region names already have their encoded name. 254 return Bytes.toStringBinary(regionName); 255 } 256 257 // old format. regionNameStr doesn't have the region name. 258 if (ri == null) { 259 return Bytes.toStringBinary(regionName) + "." + RegionInfo.encodeRegionName(regionName); 260 } else { 261 return Bytes.toStringBinary(regionName) + "." + ri.getEncodedName(); 262 } 263 } 264 265 /** 266 * @return Return a String of short, printable names for <code>hris</code> (usually encoded name) 267 * for us logging. 268 */ 269 static String getShortNameToLog(RegionInfo... hris) { 270 return getShortNameToLog(Arrays.asList(hris)); 271 } 272 273 /** 274 * @return Return a String of short, printable names for <code>hris</code> (usually encoded name) 275 * for us logging. 276 */ 277 static String getShortNameToLog(final List<RegionInfo> ris) { 278 return ris.stream().map(RegionInfo::getEncodedName).collect(Collectors.toList()).toString(); 279 } 280 281 /** 282 * Gets the table name from the specified region name. 283 * @param regionName to extract the table name from 284 * @return Table name 285 */ 286 @InterfaceAudience.Private 287 // This method should never be used. Its awful doing parse from bytes. 288 // It is fallback in case we can't get the tablename any other way. Could try removing it. 289 // Keeping it Audience Private so can remove at later date. 290 static TableName getTable(final byte[] regionName) { 291 int offset = -1; 292 for (int i = 0; i < regionName.length; i++) { 293 if (regionName[i] == HConstants.DELIMITER) { 294 offset = i; 295 break; 296 } 297 } 298 if (offset <= 0) { 299 throw new IllegalArgumentException("offset=" + offset); 300 } 301 byte[] buff = new byte[offset]; 302 System.arraycopy(regionName, 0, buff, 0, offset); 303 return TableName.valueOf(buff); 304 } 305 306 /** 307 * Gets the start key from the specified region name. 308 * @return Start key. 309 */ 310 static byte[] getStartKey(final byte[] regionName) throws IOException { 311 return parseRegionName(regionName)[1]; 312 } 313 314 /** 315 * Figure if the passed bytes represent an encoded region name or not. 316 * @param regionName A Region name either encoded or not. 317 * @return True if <code>regionName</code> represents an encoded name. 318 */ 319 @InterfaceAudience.Private // For use by internals only. 320 public static boolean isEncodedRegionName(byte[] regionName) { 321 // If not parseable as region name, presume encoded. TODO: add stringency; e.g. if hex. 322 return parseRegionNameOrReturnNull(regionName) == null && regionName.length <= MD5_HEX_LENGTH; 323 } 324 325 /** 326 * Returns A deserialized {@link RegionInfo} or null if we failed deserialize or passed bytes null 327 */ 328 @InterfaceAudience.Private 329 static RegionInfo parseFromOrNull(final byte[] bytes) { 330 if (bytes == null) return null; 331 return parseFromOrNull(bytes, 0, bytes.length); 332 } 333 334 /** 335 * Returns A deserialized {@link RegionInfo} or null if we failed deserialize or passed bytes null 336 */ 337 @InterfaceAudience.Private 338 static RegionInfo parseFromOrNull(final byte[] bytes, int offset, int len) { 339 if (bytes == null || len <= 0) return null; 340 try { 341 return parseFrom(bytes, offset, len); 342 } catch (DeserializationException e) { 343 return null; 344 } 345 } 346 347 /** 348 * @param bytes A pb RegionInfo serialized with a pb magic prefix. 349 * @return A deserialized {@link RegionInfo} 350 */ 351 @InterfaceAudience.Private 352 static RegionInfo parseFrom(final byte[] bytes) throws DeserializationException { 353 if (bytes == null) return null; 354 return parseFrom(bytes, 0, bytes.length); 355 } 356 357 /** 358 * @param bytes A pb RegionInfo serialized with a pb magic prefix. 359 * @param offset starting point in the byte array 360 * @param len length to read on the byte array 361 * @return A deserialized {@link RegionInfo} 362 */ 363 @InterfaceAudience.Private 364 static RegionInfo parseFrom(final byte[] bytes, int offset, int len) 365 throws DeserializationException { 366 if (ProtobufUtil.isPBMagicPrefix(bytes, offset, len)) { 367 int pblen = ProtobufUtil.lengthOfPBMagic(); 368 try { 369 HBaseProtos.RegionInfo.Builder builder = HBaseProtos.RegionInfo.newBuilder(); 370 ProtobufUtil.mergeFrom(builder, bytes, pblen + offset, len - pblen); 371 HBaseProtos.RegionInfo ri = builder.build(); 372 return ProtobufUtil.toRegionInfo(ri); 373 } catch (IOException e) { 374 throw new DeserializationException(e); 375 } 376 } else { 377 throw new DeserializationException("PB encoded RegionInfo expected"); 378 } 379 } 380 381 static boolean isMD5Hash(String encodedRegionName) { 382 if (encodedRegionName.length() != MD5_HEX_LENGTH) { 383 return false; 384 } 385 386 for (int i = 0; i < encodedRegionName.length(); i++) { 387 char c = encodedRegionName.charAt(i); 388 if (!((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9'))) { 389 return false; 390 } 391 } 392 return true; 393 } 394 395 /** 396 * Check whether two regions are adjacent; i.e. lies just before or just after in a table. 397 * @return true if two regions are adjacent 398 */ 399 static boolean areAdjacent(RegionInfo regionA, RegionInfo regionB) { 400 if (regionA == null || regionB == null) { 401 throw new IllegalArgumentException("Can't check whether adjacent for null region"); 402 } 403 if (!regionA.getTable().equals(regionB.getTable())) { 404 return false; 405 } 406 RegionInfo a = regionA; 407 RegionInfo b = regionB; 408 if (Bytes.compareTo(a.getStartKey(), b.getStartKey()) > 0) { 409 a = regionB; 410 b = regionA; 411 } 412 return Bytes.equals(a.getEndKey(), b.getStartKey()); 413 } 414 415 /** 416 * @return This instance serialized as protobuf w/ a magic pb prefix. 417 * @see #parseFrom(byte[]) 418 */ 419 static byte[] toByteArray(RegionInfo ri) { 420 byte[] bytes = ProtobufUtil.toRegionInfo(ri).toByteArray(); 421 return ProtobufUtil.prependPBMagic(bytes); 422 } 423 424 /** 425 * Use logging. 426 * @param encodedRegionName The encoded regionname. 427 * @return <code>hbase:meta</code> if passed <code>1028785192</code> else returns 428 * <code>encodedRegionName</code> 429 */ 430 static String prettyPrint(final String encodedRegionName) { 431 if (encodedRegionName.equals("1028785192")) { 432 return encodedRegionName + "/hbase:meta"; 433 } 434 return encodedRegionName; 435 } 436 437 /** 438 * Make a region name of passed parameters. 439 * @param startKey Can be null 440 * @param regionid Region id (Usually timestamp from when region was created). 441 * @param newFormat should we create the region name in the new format (such that it contains its 442 * encoded name?). 443 * @return Region name made of passed tableName, startKey and id 444 */ 445 static byte[] createRegionName(final TableName tableName, final byte[] startKey, 446 final long regionid, boolean newFormat) { 447 return createRegionName(tableName, startKey, Long.toString(regionid), newFormat); 448 } 449 450 /** 451 * Make a region name of passed parameters. 452 * @param startKey Can be null 453 * @param id Region id (Usually timestamp from when region was created). 454 * @param newFormat should we create the region name in the new format (such that it contains its 455 * encoded name?). 456 * @return Region name made of passed tableName, startKey and id 457 */ 458 static byte[] createRegionName(final TableName tableName, final byte[] startKey, final String id, 459 boolean newFormat) { 460 return createRegionName(tableName, startKey, Bytes.toBytes(id), newFormat); 461 } 462 463 /** 464 * Make a region name of passed parameters. 465 * @param startKey Can be null 466 * @param regionid Region id (Usually timestamp from when region was created). 467 * @param newFormat should we create the region name in the new format (such that it contains its 468 * encoded name?). 469 * @return Region name made of passed tableName, startKey, id and replicaId 470 */ 471 static byte[] createRegionName(final TableName tableName, final byte[] startKey, 472 final long regionid, int replicaId, boolean newFormat) { 473 return createRegionName(tableName, startKey, Bytes.toBytes(Long.toString(regionid)), replicaId, 474 newFormat); 475 } 476 477 /** 478 * Make a region name of passed parameters. 479 * @param startKey Can be null 480 * @param id Region id (Usually timestamp from when region was created). 481 * @param newFormat should we create the region name in the new format (such that it contains its 482 * encoded name?). 483 * @return Region name made of passed tableName, startKey and id 484 */ 485 static byte[] createRegionName(final TableName tableName, final byte[] startKey, final byte[] id, 486 boolean newFormat) { 487 return createRegionName(tableName, startKey, id, DEFAULT_REPLICA_ID, newFormat); 488 } 489 490 /** 491 * Make a region name of passed parameters. 492 * @param startKey Can be null 493 * @param id Region id (Usually timestamp from when region was created). 494 * @param newFormat should we create the region name in the new format 495 * @return Region name made of passed tableName, startKey, id and replicaId 496 */ 497 static byte[] createRegionName(final TableName tableName, final byte[] startKey, final byte[] id, 498 final int replicaId, boolean newFormat) { 499 int len = tableName.getName().length + 2 + id.length + (startKey == null ? 0 : startKey.length); 500 if (newFormat) { 501 len += MD5_HEX_LENGTH + 2; 502 } 503 byte[] replicaIdBytes = null; 504 // Special casing: replicaId is only appended if replicaId is greater than 505 // 0. This is because all regions in meta would have to be migrated to the new 506 // name otherwise 507 if (replicaId > 0) { 508 // use string representation for replica id 509 replicaIdBytes = Bytes.toBytes(String.format(REPLICA_ID_FORMAT, replicaId)); 510 len += 1 + replicaIdBytes.length; 511 } 512 513 byte[] b = new byte[len]; 514 515 int offset = tableName.getName().length; 516 System.arraycopy(tableName.getName(), 0, b, 0, offset); 517 b[offset++] = HConstants.DELIMITER; 518 if (startKey != null && startKey.length > 0) { 519 System.arraycopy(startKey, 0, b, offset, startKey.length); 520 offset += startKey.length; 521 } 522 b[offset++] = HConstants.DELIMITER; 523 System.arraycopy(id, 0, b, offset, id.length); 524 offset += id.length; 525 526 if (replicaIdBytes != null) { 527 b[offset++] = REPLICA_ID_DELIMITER; 528 System.arraycopy(replicaIdBytes, 0, b, offset, replicaIdBytes.length); 529 offset += replicaIdBytes.length; 530 } 531 532 if (newFormat) { 533 // 534 // Encoded name should be built into the region name. 535 // 536 // Use the region name thus far (namely, <tablename>,<startKey>,<id>_<replicaId>) 537 // to compute a MD5 hash to be used as the encoded name, and append 538 // it to the byte buffer. 539 // 540 String md5Hash = MD5Hash.getMD5AsHex(b, 0, offset); 541 byte[] md5HashBytes = Bytes.toBytes(md5Hash); 542 543 if (md5HashBytes.length != MD5_HEX_LENGTH) { 544 System.out.println( 545 "MD5-hash length mismatch: Expected=" + MD5_HEX_LENGTH + "; Got=" + md5HashBytes.length); 546 } 547 548 // now append the bytes '.<encodedName>.' to the end 549 b[offset++] = ENC_SEPARATOR; 550 System.arraycopy(md5HashBytes, 0, b, offset, MD5_HEX_LENGTH); 551 offset += MD5_HEX_LENGTH; 552 b[offset] = ENC_SEPARATOR; 553 } 554 555 return b; 556 } 557 558 /** 559 * Creates a RegionInfo object for MOB data. 560 * @param tableName the name of the table 561 * @return the MOB {@link RegionInfo}. 562 */ 563 static RegionInfo createMobRegionInfo(TableName tableName) { 564 // Skipping reference to RegionInfoBuilder in this class. 565 return new MutableRegionInfo(tableName, Bytes.toBytes(".mob"), HConstants.EMPTY_END_ROW, false, 566 0, DEFAULT_REPLICA_ID, false); 567 } 568 569 /** 570 * Separate elements of a regionName. 571 * @return Array of byte[] containing tableName, startKey and id OR null if not parseable as a 572 * region name. 573 * @throws IOException if not parseable as regionName. 574 */ 575 static byte[][] parseRegionName(final byte[] regionName) throws IOException { 576 byte[][] result = parseRegionNameOrReturnNull(regionName); 577 if (result == null) { 578 throw new IOException( 579 INVALID_REGION_NAME_FORMAT_MESSAGE + ": " + Bytes.toStringBinary(regionName)); 580 } 581 return result; 582 } 583 584 /** 585 * Separate elements of a regionName. Region name is of the format: 586 * <code>tablename,startkey,regionIdTimestamp[_replicaId][.encodedName.]</code>. Startkey can 587 * contain the delimiter (',') so we parse from the start and then parse from the end. 588 * @return Array of byte[] containing tableName, startKey and id OR null if not parseable as a 589 * region name. 590 */ 591 static byte[][] parseRegionNameOrReturnNull(final byte[] regionName) { 592 int offset = -1; 593 for (int i = 0; i < regionName.length; i++) { 594 if (regionName[i] == HConstants.DELIMITER) { 595 offset = i; 596 break; 597 } 598 } 599 if (offset == -1) { 600 return null; 601 } 602 byte[] tableName = new byte[offset]; 603 System.arraycopy(regionName, 0, tableName, 0, offset); 604 offset = -1; 605 606 int endOffset = regionName.length; 607 // check whether regionName contains encodedName 608 if ( 609 regionName.length > MD5_HEX_LENGTH + 2 && regionName[regionName.length - 1] == ENC_SEPARATOR 610 && regionName[regionName.length - MD5_HEX_LENGTH - 2] == ENC_SEPARATOR 611 ) { 612 endOffset = endOffset - MD5_HEX_LENGTH - 2; 613 } 614 615 // parse from end 616 byte[] replicaId = null; 617 int idEndOffset = endOffset; 618 for (int i = endOffset - 1; i > 0; i--) { 619 if (regionName[i] == REPLICA_ID_DELIMITER) { // replicaId may or may not be present 620 replicaId = new byte[endOffset - i - 1]; 621 System.arraycopy(regionName, i + 1, replicaId, 0, endOffset - i - 1); 622 idEndOffset = i; 623 // do not break, continue to search for id 624 } 625 if (regionName[i] == HConstants.DELIMITER) { 626 offset = i; 627 break; 628 } 629 } 630 if (offset == -1) { 631 return null; 632 } 633 byte[] startKey = HConstants.EMPTY_BYTE_ARRAY; 634 if (offset != tableName.length + 1) { 635 startKey = new byte[offset - tableName.length - 1]; 636 System.arraycopy(regionName, tableName.length + 1, startKey, 0, 637 offset - tableName.length - 1); 638 } 639 byte[] id = new byte[idEndOffset - offset - 1]; 640 System.arraycopy(regionName, offset + 1, id, 0, idEndOffset - offset - 1); 641 byte[][] elements = new byte[replicaId == null ? 3 : 4][]; 642 elements[0] = tableName; 643 elements[1] = startKey; 644 elements[2] = id; 645 if (replicaId != null) { 646 elements[3] = replicaId; 647 } 648 return elements; 649 } 650 651 /** 652 * Serializes given RegionInfo's as a byte array. Use this instead of 653 * {@link RegionInfo#toByteArray(RegionInfo)} when writing to a stream and you want to use the pb 654 * mergeDelimitedFrom (w/o the delimiter, pb reads to EOF which may not be what you want). 655 * {@link #parseDelimitedFrom(byte[], int, int)} can be used to read back the instances. 656 * @param infos RegionInfo objects to serialize 657 * @return This instance serialized as a delimited protobuf w/ a magic pb prefix. 658 */ 659 static byte[] toDelimitedByteArray(RegionInfo... infos) throws IOException { 660 byte[][] bytes = new byte[infos.length][]; 661 int size = 0; 662 for (int i = 0; i < infos.length; i++) { 663 bytes[i] = toDelimitedByteArray(infos[i]); 664 size += bytes[i].length; 665 } 666 667 byte[] result = new byte[size]; 668 int offset = 0; 669 for (byte[] b : bytes) { 670 System.arraycopy(b, 0, result, offset, b.length); 671 offset += b.length; 672 } 673 return result; 674 } 675 676 /** 677 * Use this instead of {@link RegionInfo#toByteArray(RegionInfo)} when writing to a stream and you 678 * want to use the pb mergeDelimitedFrom (w/o the delimiter, pb reads to EOF which may not be what 679 * you want). 680 * @return This instance serialized as a delimied protobuf w/ a magic pb prefix. 681 */ 682 static byte[] toDelimitedByteArray(RegionInfo ri) throws IOException { 683 return ProtobufUtil.toDelimitedByteArray(ProtobufUtil.toRegionInfo(ri)); 684 } 685 686 /** 687 * Parses an RegionInfo instance from the passed in stream. Presumes the RegionInfo was serialized 688 * to the stream with {@link #toDelimitedByteArray(RegionInfo)}. 689 * @return An instance of RegionInfo. 690 */ 691 static RegionInfo parseFrom(final DataInputStream in) throws IOException { 692 // I need to be able to move back in the stream if this is not a pb 693 // serialization so I can do the Writable decoding instead. 694 int pblen = ProtobufUtil.lengthOfPBMagic(); 695 byte[] pbuf = new byte[pblen]; 696 if (in.markSupported()) { // read it with mark() 697 in.mark(pblen); 698 } 699 700 // assumption: if Writable serialization, it should be longer than pblen. 701 IOUtils.readFully(in, pbuf, 0, pblen); 702 if (ProtobufUtil.isPBMagicPrefix(pbuf)) { 703 return ProtobufUtil.toRegionInfo(HBaseProtos.RegionInfo.parseDelimitedFrom(in)); 704 } else { 705 throw new IOException("PB encoded RegionInfo expected"); 706 } 707 } 708 709 /** 710 * Parses all the RegionInfo instances from the passed in stream until EOF. Presumes the 711 * RegionInfo's were serialized to the stream with oDelimitedByteArray() 712 * @param bytes serialized bytes 713 * @param offset the start offset into the byte[] buffer 714 * @param length how far we should read into the byte[] buffer 715 * @return All the RegionInfos that are in the byte array. Keeps reading till we hit the end. 716 */ 717 static List<RegionInfo> parseDelimitedFrom(final byte[] bytes, final int offset, final int length) 718 throws IOException { 719 if (bytes == null) { 720 throw new IllegalArgumentException("Can't build an object with empty bytes array"); 721 } 722 List<RegionInfo> ris = new ArrayList<>(); 723 try (DataInputBuffer in = new DataInputBuffer()) { 724 in.reset(bytes, offset, length); 725 while (in.available() > 0) { 726 RegionInfo ri = parseFrom(in); 727 ris.add(ri); 728 } 729 } 730 return ris; 731 } 732 733 /** Returns True if this is first Region in Table */ 734 default boolean isFirst() { 735 return Bytes.equals(getStartKey(), HConstants.EMPTY_START_ROW); 736 } 737 738 /** Returns True if this is last Region in Table */ 739 default boolean isLast() { 740 return Bytes.equals(getEndKey(), HConstants.EMPTY_END_ROW); 741 } 742 743 /** 744 * @return True if region is next, adjacent but 'after' this one. 745 * @see #isAdjacent(RegionInfo) 746 * @see #areAdjacent(RegionInfo, RegionInfo) 747 */ 748 default boolean isNext(RegionInfo after) { 749 return getTable().equals(after.getTable()) && Bytes.equals(getEndKey(), after.getStartKey()); 750 } 751 752 /** 753 * @return True if region is adjacent, either just before or just after this one. 754 * @see #isNext(RegionInfo) 755 */ 756 default boolean isAdjacent(RegionInfo other) { 757 return getTable().equals(other.getTable()) && areAdjacent(this, other); 758 } 759 760 /** Returns True if RegionInfo is degenerate... if startKey > endKey. */ 761 default boolean isDegenerate() { 762 return !isLast() && Bytes.compareTo(getStartKey(), getEndKey()) > 0; 763 } 764 765 /** 766 * @return True if an overlap in region range. 767 * @see #isDegenerate() 768 */ 769 default boolean isOverlap(RegionInfo other) { 770 if (other == null) { 771 return false; 772 } 773 if (!getTable().equals(other.getTable())) { 774 return false; 775 } 776 int startKeyCompare = Bytes.compareTo(getStartKey(), other.getStartKey()); 777 if (startKeyCompare == 0) { 778 return true; 779 } 780 if (startKeyCompare < 0) { 781 if (isLast()) { 782 return true; 783 } 784 return Bytes.compareTo(getEndKey(), other.getStartKey()) > 0; 785 } 786 if (other.isLast()) { 787 return true; 788 } 789 return Bytes.compareTo(getStartKey(), other.getEndKey()) < 0; 790 } 791 792 default int compareTo(RegionInfo other) { 793 return RegionInfo.COMPARATOR.compare(this, other); 794 } 795}